Software engineering
Database
Java
Verifica e convalida

TODO PRENDERE INFO EXTRA DA

  • test driven development by example di Beck
  • e dal GOF

All page numbers (i.e. pag.) are from GoF's.

Abstract factory

La dependency injection e' basata sull'Abstract Factory.

ESPANDERE con abstract factory

Adapter - page 139

Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.

Class Adapter

Adatto una singola classe, staticamente, quindi proprio "quella classe".
"Un unico oggetto che puo' essere usato contemporaneamente con le due interfacce diverse (vecchia e nuova)".
Utile se devo adattare solo 1 metodo su 10: gli altri 9 mi vengono gratis, devo fare fatica solo su 1.

class adapter

Object Adapter

Adatto un oggetto compatibile con una interfaccia, una gerarchia; perche' posso settare diversi adapter, in base all'esigenza.
Due oggetti diversi.
Il nuovo non puo' piu' essere usato con interfaccia vecchia.

object-adapter

Si differenzia perche' sfrutta la composizione.
Posso aggregare piu' classi diversi, quindi c'e' maggiore flessibilita'.

Builder

Telescoping constructor

Si hanno multipli costruttori in overloading.
Di difficile manutenibilita' nel momento in cui voglio diverse possibilita', perche' aumentano esponenzialmente i costruttori da dichiarare.

Setter (o fluent interface)

Problemi di concorrenza e sono obbligato ad accettare la mutabilita' dell'oggetto, perche' ho dei setter, quindi espongo lo stato.

Static method factory (non e' Factory method)

Ho un costruttore privato con tutti i parametri, e una serie di metodi statici pubblici che al loro interno costruiscono l'oggetto in base a cio' che mi serve.
Posso dare nomi significativi a ogni metodo, per spiegare come crea l'oggetto, cosa che con il costruttore chiaramente non posso fare.

Builder pattern

builder-pattern

Classe Builder interna, con costruttore con soli parametri obbligatori. Si hanno poi variabili d'istanza con valori di default, sovrascrivibili tramite metodo, usando fluent interface.

public class Xxx { 
  private final TO optionalField1; 
  private final T1 mandatoryField; 
  private final T2 optionalField2; 
  
  private Xxx(Builder builder) { 
    mandatoryField = builder.mandatoryField; 
    optionalField1 = builder.optionalField1; 
    optionalField2 = builder.optionalField2;
  }
     
  public static class Builder { 
    private TO optionalField1 = defaultValue1;
    private T1 mandatoryField; 
    private T2 optionalField2 = defaultValue2;
    public Builder(T1 mf) { 
      mandatoryField = mf;
    } 
    public Builder withOptionalField1(TO of) {
       optionalFieldl = of; 
       return this;
    }
    // etc...

Usage

Xxx b = Xxx.Builder(mandatoryField)
            .withOptionalField1(optionalField)
            .build();

Chain of responsibility

Disaccoppia chi genera la richiesta da chi puo' soddisfarla.

"Permette di definire una catena di potenziali gestori di una richiesta, non sappiamo a priori chi sara' in grado di gestirla effettivamente"

Evita l'antipattern dello switch case o degli if else

Ogni valutatore guarda se sa rispondere alla domanda, altrimenti manda avanti la domanda al prossimo: c'e' uno specifico ordine in cui le risposte sono ordinate.

Rimuovo la responsabilita' dalla classe client dell'identificazione di una certa situazione, la responsabilita' non e' sua. Il client e' chiuso rispetto alle modifiche ma aperto alle estensioni, perche' se devo cambiare la logica di gestione delle responsabilita' lui non viene toccato.

ESPANDERE
Analogia con il dynamic binding e il suo albero di navigazione per cercare l'implementazione del metodo.

Ottimo anche quando sviluppato in TDD, in quanto ogni nodo puo' essere sviluppato in isolamento.

Composite

composite-pattern

Gestire uniformemente foglie e sotto-alberi.
"Non mi devo accorgere se sto interagendo con una foglia o un sotto-albero".

Decorator

pattern-decorator

Aggiungere nuove funzionalita' o caratteristiche dinamicamente.
Distribuisce le responsabilita' lasciando ogni decorazione molto semplice.

Simile come struttura al Decorator, il composite mette assieme tante "cose", il Decorator decora una "cosa".
Notare l'1 nello schema, vicino a Component.

Possono esserci piu' ConcreteDecorator.

L'elemento finale di un Decorator pattern e' cio' che sto decorando, che puo' arrivare anche dopo una sequenza di decorazioni.

I vari oggetti decorati sono estranei gli uni agli altri.

Un esempio in Java sono gli InputStream.

Esempio di Decorator dal prof

esempio-decorator

Iterator

Fornisce un modo di accedere agli elementi di un oggetto aggregatore in maniera sequenziale senza esporre la rappresentazione interna

iterator

Notare come condimentCost non dipende da chi sto decorando, dipende da ConcreteCondiment.

Facade

Fornisce una interfaccia unificata e semplificata a un insieme di interfacce separate.

Factory method

Definisce un'interfaccia per creare un oggetto ma lascia alle sottoclassi la scelta su cosa creare.

factory-pattern

Model View Controller

L'MVC e' un set di pattern che collaborano assieme nello stesso design: Composite (view), Strategy (quale Controller iniettare, il Controller e' lo Strategy per la View), Observer (il Model e' observable e View e Controller sono observer).

The best way to think of MVC is as set of principles including the separation of presentation from domain logic and synchronizing presentation state through events (the observer pattern)
-- Martin Fowler

About "handling the triggering of synchronization between screen state and session state", MVC does it by making updates on the model and then relying of the observer relationship to update the views that are observing that model.

Il problema e' che c'e' una circolarita' di comunicazione, quindi si potrebbe preferire per questo l'MVP.

Model

Un unico modello che rappresenta lo stato dell'applicazione

View

La View si aggiorna ogni volta che il Model cambia stato.
Tra Model e View c'e' una relazione tipo Observer.

Controller

Ascolta gli eventi dell'interfaccia, a fronte del quale eventualmente puo' richiedere una modifica dello stato.
Implementa quindi la Strategy di gestione di un evento. A seconda del Controller che associo ho un comportamento diverso.

Model View Presenter

mvc-mvp

Viene rimossa la dipendenza circolare.
Molto piu' facile da testare.

Il Presenter ha un riferimento a View e Model, agendo da middle man.

Per ogni View c'e' un Presenter.
Si puo' intendere quindi 1 classe View e 1 istanza Presenter, 1 classe View e 1 classe Presenter, tenendo presente i principi di buona scrittura del software.

Vedi Modalita' - pag 298 di Observer.

Diverse relazioni

Presenter Model

Il Presenter viene aggiunto come ascoltatore di cambi di stato sul Model.

model.addObserver(presenter)

Presenter View

Il presente viene aggiunto come gestore degli eventi sulle View

view.addHandler(presenter)

Suddivisione tra Model e State

Puo' essere una buona idea operare una divisione di questo tipo:

State: rappresenta lo stato all'interno del pattern.
Model: e' Class Adapter dello State aggiungendo capacita' di Observer - pag 293.

Interface Segregation sul Presenter

Non sempre voglio che tutti i Presenter implementino lo stesso Presenter, ricordarsi di specializzare sfruttando l'eredita' multipla sulle interfacce.

Da dove si parte nell'implementazione?

Si puo' approcciare da diversi lati, ad esempio con diversi sotto-team che si avvicinano assieme al risultato.

O inizio dal Presenter (top down). Piuttosto che dal Model (bottom up).
In questo senso una buona strategia puo' essere partire dal Model delineando in una interfaccia quali sono i metodi che verranno implementati.

TODO RIMANDO A IMMUTABILITA'
Favorire un design con immutabilita' nel Model, per evitare deep copies.

Gestione degli errori

Nel Presenter (ma...).
Importante che una volta validato il dato questa validazione non sia fatta, se si usa il contract based allora si puo' costruire un tipo che assume per costruzione la validazione del dato, es:

public record TimeOfRun(@NotNull String name, @NotNull Double time) {
  public TimeOfRun {
    if (name.isBlank) throw
    if (time < 0) throw
  }
}

E' il Presenter che istruisce la vista sul suo stato di errore.

Ma anche nel Model (...!).
Dipende da dove ha senso, ad esempio se ho un Model che gestisce due manche di sciatori, e mi arriva nel Model un nome di uno sciatore non presente nella prima manche allora questo lo posso gestire solo nel Model.

Deregistrarsi dagli observer

Puo' avere senso ad un certo stato di avanzamento nell'applicazione per qualcuno di deregistrarsi.

NullObject

Vogliamo creare un oggetto che corrisponda al concetto "nessun valore" o "valore neutro"

public interface CardSource {
    Card draw();
    boolean isEmpty();

    enum NULL implements CardSource {
        INSTANCE;
        public boolean isEmpty() {
            return true;
        }
        public Card draw() {
            assert !isEmpty();
            return null;
        }
    }
}

Observer - pag 293

Diversi modi di presentare una informazione, esempio
observer

...il problema e' che ogni vista e' accoppiata alle altre perche' devono reagire al cambiamento
observer

Per evitare questo estraiamo la parte comune: lo stato, e lo mettiamo in un oggetto a parte (Subject), osservato dagli altri (Observer)

observer-pattern

Vogliamo rendere osservabile questo stato

public class State {
    private double temp;
    
    public State(double temp) {
        this.temp = temp;
    }
}

Modalita' - pag 298

E' possibile avere due modalita' per avvertire gli observer di un cambiamento: pull e push

@Override
public void setTemp(double temp) {
    if (this.temp != temp) {
        this.temp = temp;
        notifyObservers();
    }
}

@Override
public void notifyObservers() {
    for (Observer<Double> obs : observers) {
        obs.update(this, temp); // qui si rendono disponibili entrambe le modalita'
    }
}

Stato semplice push.
Stato complesso o parzialmente rilevante pull.

Modalita' pull

Il subject fornisce metodi (getter) per accedere al proprio stato, lasciando agli observer il compito di scegliere cosa consultare.
Vantaggi: Più flessibile, perché ogni observer può decidere cosa gli serve. Ideale per stati complessi.

Modalita' push

Il subject invia lo stato agli observer.
Vantaggi: semplice da implementare se lo stato è compatto e se gli observer richiedono tutte le informazioni.
Svantaggi: inefficiente se lo stato è complesso e gli observer sono interessati solo a una parte, o se vogliono solo essere notificati del cambiamento senza conoscerne i dettagli.

@Override
public void update(@Nullable Observable<Double> subject, @NotNull Double state) {
    view.setValue(String.format("%.2f", strategy.convertFromCelsius(state)));
}

Singleton

Sfrutta il fatto che in Java i campi degli enum sono realizzati tramite degli oggetti costanti creati al momento del loro primo uso

public enum Singleton {
    INSTANCE;
    public void op() { ... }
}

Singleton.INSTANCE.op();

State

pattern-state

Nei diversi momenti ci sono diversi stati da poter utilizzare (state machine).

Meta stato (nell'esempio State)
Stato astratto classe (nell'esempio ConcreteState)
Stato concreto le informazioni di contesto

Gli stati non devono conoscersi a vicenda, e' il Context che puo' conoscerli.

Strategy

pattern-strategy

Definisce una famiglia di algoritmi, e li rende (tramite encapsulation) tra loro intercambiabili.

Ad esempio usato nel sorting.

Template

TODO ESPANDERE

Design Pattern

Comparable

private static final Comparator<TimeOfRun> COMPARATOR = Comparator
    .comparingDouble(TimeOfRun::time)
    .reversed() // refers to evertything until this point
    .thenComparing(tr -> tr.name)
    .reversed() // refers to evertything until this point

Bene disambiguare su tutti i campi. Perche' alcune classe che assumono l'implementazione di Comparable<T> potrebbero usare compareTo per vedere ad esempio se la chiave e' gia' esistente in una struttura dati.

Functional interface

TODO

Exception

Difference between checked and unchecked

Unchecked: se avvengono il programma non e' recoverable, quindi non ha senso fare try catch. Per lo piu' sono errori del programmatore.

try with resources

// ci assicura che lo scanner venga chiuso
try (Scanner sc = new Scanner(input)) {
  // code
}

ParameterizedTest

Un ottimo modo per evitare duplicazione all'interno dei test. Anche i test sono codice.

Dynamic binding

Si tratta del modo con cui viene selezionato il metodo a partire da un tipo:

  • collegamento statico: viene usato il tipo dichiarato dell'identificatore e quindi selezionato il metodo basato su questo tipo.
    Questo metodo e' efficiente perche' l'indirizzo del metodo da chiamare e' determinato a compile time; non tiene conto pero' di ereditarieta' o overriding
  • collegamento dinamico: il metodo viene chiamato sul tipo effettivo, non sul tipo apparente
Java

Nullability e' la possibilità di assegnare un valore speciale null ad una variabile che indica un riferimento ad un oggetto.
Il tentativo di de-referenziare un null porta una NullPointerException.

null e' assegnabile a qualsiasi variabile di tipo riferimento, ne e' sottotipo. Quindi questo vuole dire che se non si interviene in qualche modo il compilatore non reagirà in alcun modo, e a runtime ci sarà una eccezione non gestita, con il conseguente halt dell'applicazione.

null per se non e' il problema, il problema e' il fatto che e' assegnabile a qualsiasi tipo riferimento.

Mitigare

Per mitigare questa situazione ci sono diverse soluzioni:

  • NullObject pattern - da usare quando c'e' il concetto di "nessun valore" o "valore neutro", quindi posso implementare dei metodi che rispondano a questa situazione
  • assertion - per esprimere il design by contract spiegato da Meyer, a runtime non ci sono più
  • annotazione @NotNull e @Nullable - per esprimere il design by contract spiegato da Meyer, a runtime non ci sono più, inoltre sono gestibili da tool di analisi statica che possono usarle per fornire indicazioni utili durante lo sviluppo

"[...] But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement."
-- Sir Tony Hoare

Gestione

Si può gestire in due modi

  • contract based
  • programmazione difensiva

Un codice non dovrebbe far uso di null nelle parti visibili, di interfaccia

Fail fast: Objects.requireNonNull esplicita che non accetto qualcosa come null, gestendolo sollevando una NullPointerException, pero' viene eseguito sempre, anche in produzione, può avere senso ai bordi di un contratto

public Card(Rank rank, Suit suit) {
    this.rank = Object.requireNonNull(rank);
    this.suit = Object.requireNonNull(suit);
  }
}

Difensiva: gestione esplicita dei null, "sono capace di gestire null", programmazione difensiva, pero' viene eseguito sempre, anche in produzione, può avere senso ai bordi di un contratto

public Card(Rank rank, Suit suit) {
  if (rank == null || suit == null) {
    throw new IllegalArgumentException();
  }
  
  this.rank = rank;
  this.suit = suit;
}

Mi assicuro che in sviluppo non mi arrivi un null, ma non e' compito mio:

public Card(Rank rank, Suit suit) {
  assert rank != null && suit != null;
  this.rank = rank;
  this.suit = suit;
}

Intellij può generare if in questo caso, inoltre lascia aperta la porta a tool che potrebbero fare analisi statica grazie alla mia dichiarazione di intento:

private final @NotNull Rank rank;
private final @NotNull Suit suit;

public Card(@NotNull Rank rank, @NotNull Suit suit) {
  this.rank = rank;
  this.suit = suit;
}

Optional e' pensato per essere utilizzato nei tipi di ritorno, non troppo diverso da avere la necessita' di testare per null, sebbene sia espressa dal tipo la necessita' di gestire la situazione

Il seguente codice invece e' chiaramente completamente sbagliato, perché viene comunque creato un oggetto con le variabili d'istanza a null

public Card(Rank rank, Suit suit) {
  if (rank != null && suit != null) {
    this.rank = rank;
    this.suit = suit;
  }
}
Nullability

The point of encapsulation isn't really about hiding the data, but in hiding design decisions, particularly in areas where those decisions may have to change

Parnas' Law (L8): Solo cio' che e' nascosto puo' essere cambiato liberamente e senza pericoli
  • possibilita' di evoluzione: perche' il fatto che nessuno conosca come faccio qualcosa all'interno, a parita' di effetti esterni, mi da liberta' di cambiare scelta.
  • facilitare la comprensione del codice: isolo la parte di codice in cui devo cercare, solo certe parti del codice hanno permesso di scrivere in quella variabile (definisco le responsabilita': Single responsibility principle)
  • "A class is more reusable when you minimize the assumptions other classes must make to use it"

Reference escaping

Reference escaping - "pensavo di essere protetto dall'encapsulation, ma invece e' peggio perche' e' difficile da trovare"

Non sempre c'e' reference escaping, dipende se cio' che sto passando e' o meno un segreto, se non e' un segreto o se e' immutabile allora non c'e'.

Tipologie di relazioni:

  • composizione: tipicamente non voglio che l'oggetto interno della classe venga esposto, perche' e' un mio segreto
  • associazione: se qualcosa arriva da fuori, mi e' passato, probabilmente non e' un mio segreto, quindi posso tenermene una copia, ma se lo rendo disponibile non c'e' reference escaping
  • aggregazione:

Lo stato del Model, in Model View Presenter e' un segreto da proteggere, perche' se qualcuno lo modifica dall'esterno non posso notificare gli osservatori.

Variabili di istanza non private

Normalmente le variabili di istanza devono essere private, chiaramente non vuol dire mettere getter e setter. Vedere lo stato puo' essere corretto, cio' che non e' corretto e' consentire la modifica.

Tenere un riferimento esterno alla classe

Si prende un riferimento esterno senza farsene una copia. Chiaramente puo' succedere nei punti di contatto con l'esterno come setter o costruttori.

Immutabilita'

Oggetti immutabili possono essere condivisi.

Cosa vuol dire essere immutabili?
Non vuol dire essere stateless, vuol dire che non c'e' modo di cambiare lo stato dell'oggetto dopo la sua inizializzazione. String e' immutabile ma una volta creata non la modifico piu'.

Encapsulation e astrazioni

Dare un nome ai concetti: type abstraction.

Tell dont ask

Things that are tightly coupled should be in the same component. Thinking of tell-don't-ask is a way to help programmers to see how they can increase this co-location.

Qui ne parlano un po' più approfonditamente:

Adhering to this notion of “Tell, Don’t Ask” is easier if you mentally categorize each of your functions and methods as either a command or a query

"Non chiedere i dati, ma di cosa vuoi che faccia sui dati"; vuol dire ad esempio minimizzare i getter e creare una funzione che provveda al vero obiettivo per il quale abbiamo creato il getter.

[...] start by designing classes based on their responsibilities, you can then progress naturally to specifying commands that the class may execute, as opposed to queries that inform you as to the state of the object.

Open Closed principle

We expect that the methods of a class are not closed to changes in the member variables of that class. However we do expect that any other class, including subclasses are closed against changes to those variables. We have a name for this expectation, we call it: encapsulation

Protected Variation pattern

Identify points of predicted variation and create a stable interface around them.

Encapsulation and information hiding

To ask is a query, to tell is a command

This principle concerns how objects interact, specifically regarding state and behavior. It emphasizes telling an object what to do, rather than asking it for its state and then making decisions based on that state externally.

Rule: instead of querying an object's state and then performing actions based on that state in the calling code, you should delegate that responsibility to the object itself by providing methods that encapsulate the action and the decision-making logic.

Goal: to improve encapsulation and place behavior closer to the data it operates on. This makes objects more autonomous and responsible for their own state and the rules governing it.

Example

Asking

if (account.getBalance() > withdrawalAmount) { 
	account.debit(withdrawalAmount); 
}

Telling:

account.withdraw(withdrawalAmount);

The logic if (balance > amount) is inside the withdraw method of the Account class.

Difference with Law of Demeter

Following "Tell, Don't Ask" often helps you adhere to the "Principle of Least Knowledge." When you tell an object to perform an operation (account.withdraw(amount)), you don't need to ask for its internal details (account.getBalance()), which might involve navigating through other objects, potentially violating LoD.

LoD is primarily about the structure of collaborations (who talks to whom), while TDA is about the nature of the interaction (commands vs. queries+decisions).

LoD restricts the scope of interactions, while TDA guides the style of interaction towards commands rather than state queries. Both contribute to building more robust, maintainable, and less coupled object-oriented systems.

Martin Fowler

For me, tell-don't-ask is a stepping stone towards co-locating behavior and data, but I don't find it a point worth highlighting.

Follow the links in the article because there's more to be learned.

Tell don't ask

When considering whether a particular design is appropriate or not, one must not simply view the solution in isolation. One must view it in terms of the reasonable assumptions that will be made by the users of that design.

Single responsibility principle

A class should have one, and only one, reason to change

Why was it important to separate these two responsibilities into separate classes? Because each responsibility is an axis of change.
When the requirements change, that change will be manifest through a change in responsibility amongst the classes. If a class assumes more than one responsibility, then there will be more than one reason for it to change. If a class has more then one responsibility, then the responsibilities become coupled. Changes to one responsibility may impair or inhibit the class’ ability to meet the others. This kind of coupling leads to fragile designs that break in unexpected ways when changed.

"Each software module should have one and only one reason to change", but then "What defines a reason to change?".

"Incoraggia un'alta coesione interna (tutti i metodi hanno a che fare con lo stesso obiettivo) e un alto disaccoppiamento esterno, porta ad una buona modularizzazione"

"This principle is about people."

This is the reason we do not put SQL in JSPs. This is the reason we do not generate HTML in the modules that compute results. This is the reason that business rules should not know the database schema. This is the reason we separate concerns.

"Gather together the things that change for the same reasons. Separate those things that change for different reasons."

  • the secret which I am guaranteeing for, and for which I manage evolution over time; or
  • the single reason why I should change

img

Benefici

  • ridotto impatto a seguito di modifiche perche' ogni classe si concentra su uno specifico aspetto o funzionalita' del sistema
  • mantenibilita' aumentata perche' posso intervenire su una classe, ad esempio con un refactor, senza che queste modifiche impattino su un'altra
  • facilita il riutilizzo perche' siccome e' chiaro di cosa si occupano e non hanno un alto livello di accoppiamento con le altre classi, posso usarle anche in altri ambiti
  • Decorator: per decentralizzare le varie aggiunte, ognuna in una classe decoratrice, invece di avere una god class
  • Strategy: per scegliere quale algoritmo usare, la strategia si occupa di un certo compito, ignorando cio' che ha attorno
  • Factory: delego la creazione di ogni specifico oggetto

Open-Closed principle

The foundation for building code that is maintainable and reusable

You should be able to extend a classes behavior, without modifying it

When the creation of a derived class causes us to make changes to the base class, it often implies that the design is fault, indeed it violates the Open-Closed principle

Permette di ottenere:

  • stabilita' grazie al fatto che non vengono modificate le classi
  • mantenibilita' attraverso l'estensibilita'

Dynamic binding e' un aspetto chiave di OOP, perche' permette di chiamare codice non ancora scritto: cioe' riconoscendo il tipo concreto dal tipo apparente solo a runtime, tengo aperta la possibilita' di future estensioni.

  • Template: definisce lo scheletro di un algoritmo in una classe base, alcuni passaggi vengono lasciati da implementare alle sottoclassi, senza modificare la classe originale
  • Adapter - page 139: riusare classi in nuovi contesti senza modificarle direttamente
  • Strategy: ogni algoritmo e' incapsulato nella sua classe che implementa una interfaccia condivisa

Liskov substitution principle

if you have a program that works correctly with a base class, then it should continue to work correctly if you replace the base class with any of its derived classes

S sottotipo di T:

  • precondizioni dei metodi di S non devono essere piu' stringenti delle precondizioni dei metodi di T
  • postcondizioni dei metodi di S non devono essere piu' larghe delle postcondizioni dei metodi di T

Voglio evitare che chi cita una classe e vede una possibilita' (metodo), si ritrovi con una classe figlia che questa possibilita' non ce l'ha.

If a function violates LSP then that function uses a reference to a base class, but must know about all the derivatives of that base class.

Open-Closed Principle and Liskov Substitution Principle

In order for the LSP to hold, and with it the Open-Closed principle, all derivatives must conform to the behavior that clients expect of the base classes that they use.

Siamo interessati al comportamento dei moduli tra di loro, comportamento su cui gli utilizzatori dipendono.

Design by Contract and Liskov Substituion Principle

In Design by Contract (Bertrand Meyer) methods declare preconditions and postconditions:

  • preconditions must be true before method invocation
  • postconditions are guaranteed by method invocation

The rule for these conditions, for derivatives, is:

...when redefining a routine in a derivative, you may only replace its precondition by a weaker one, and its postcondition by a stronger one.

So:

  • when using an object through its base class the client knows only the preconditions and postconditions of the base class derived objects must not expect such clients to obey preconditions that are stronger that those required by the base class
    They must accept anything that the base class accepts
  • derived classes must conform to all the postconditions of the base their behaviours and outputs must not violate any of the contraints established for the base class; users of the base class must not be confused by the output of the derived class

Interface segregation

Clients should not be forced to depend on methods they do not use

Make fine grained interfaces that are client specific

"Offer different views of the same type."

"Polimorfismo e' un modo per esprimere di fronte alla stessa interfaccia comportamenti diversi. Grazie al polimorfismo possiamo mostrare lo stesso cosa nascondendo diversi come."

-- Jessica Vecchia

Fa in modo che il client ottenga la dipendenza minima dalla classe che vuole utilizzare, vuole utilizzare solo certi aspetti che la classe implementa, non tutti.

Questo principio permette di raggiungere un grado basso di accoppiamento tra gli oggetti.

interface-segregation

  • Observer: un solo metodo update() che e' l'unico interesse del Subject

Dependency inversion

Depend on abstractions, not on concretions

I moduli di alto livello non dovrebbero dipendere dai moduli di basso livello: entrambi dovrebbero dipendere da astrazioni.
Le astrazioni non dovrebbero dipendere dai dettagli. Programmare verso le interfacce.

"Depend on stuff more concrete than me"

Benefici

  • testabilita' perche' posso mockare con semplificita' una interfaccia invece di portarmi dietro un intero albero di implementazioni
  • flessibilita' perche' posso variare l'implementazione in base alle necessita', quindi ad esempio passare un database PostgreSQL a MySQL
  • ridotto accoppiamento perche' dei cambiamenti nei moduli di basso livello non impattano i moduli di alto livello
  • Factory method: definisce una interfaccia per la creazione di un oggetto, ma consente alle sottoclassi di decidere quale classe istanziare
  • Abstract factory: fornisce un'interfaccia per la creazione di famiglie di oggetti correlati o dipendenti senza specificare le loro classi concrete
  • Composite: trattare uniformemente parti e insiemi, il client non sa se sta parlando con una parte o un insieme

Esempi

Dependency Inversion e Open Close

Identificare gli aspetti della applicazione che cambiano e separarli da cio' che rimane fisso

In questo caso usiamo due volte lo Strategy.

strategy

Fin qua tutto ok.

strategy-problem

Il problema nasce quando si aggiunge RubberDuck e successivamente si aggiunge un metodo fly() a Duck: RubberDuck si ritrova a poter volare!

Override non e' una soluzione perche' mi ritrovo a dover implementare uno "stub" dentro RubberDuck, sto violando Liskov substitution principle: ho una sottoclasse che non sa fare qualcosa che la superclasse sa fare.

Definire diverse interfacce come Quackable, Swimmable, ..., con metodi di default e' meglio ma devo far implementare a MallardDuck e RedheadDuck le interfacce in base a cosa possono fare, ma mi porta a duplicazione.

Delego!
pattern-strategy-ducks
Duck contiene (aggrega) al suo interno come si comporta rispetto al fare "quack".

Quindi non devo avere piu' ragioni per cambiare: "cambia modo di fare quack o di volare?" se sono nella classe sono due motivi diversi per cambiare la classe.

SOLID

Demetra, dea greca della natura, dei raccolti, e delle messi<label class="sidenote-toggle sidenote-number"></label><span class="sidenote">Cosi come i raccolti crescono in campi appositi, i moduli nel software devono operare all’interno dei loro campi di interazione, minimizzando le dipendenze, riducendo il diffondersi della conoscenza del loro funzionamento interno</span>

Obiettivo

Obiettivo e' ridurre l'accoppiamento tra gli oggetti e promuovere la modularità del codice.
E' anche chiamato principle of least knowledge, o in modo colloquiale riassunto con "gli amici dei miei amici non sono miei amici".
Anche citato in "tell don't ask".

Maggiore e' il numero di oggetti con cui parli, maggiore e' la probabilità di rompersi quando questi oggetti cambiano.

The fundamental goal [...] is to write shy code, code that doesn’t reveal too much of itself to anyone else and doesn’t talk to others any more than is necessary. Shy code keeps to itself, not like that gossipy neighbor who’s involved in everyone else’s comings and goings.

Istruzioni

Limitare le interazioni tra gli oggetti ad un numero ristretto di amici stretti.
Meno conosco gli altri oggetti piu' e' facile che possano cambiare.
Il codice di un metodo dovrebbe accedere soloLimitazioni analoghe a quella che in programmazione funzionale e' una funzione pura a:

  • this
  • i parametri
  • oggetti creati all'interno del metodo
  • (solo se proprio necessario) oggetti disponibili globalmente

Un esempio di non applicazione potrebbe essere: "Faccio un get, manipolo cio' che ho ottenuto, e faccio un set."

Law of Demeter

Protected variation

Open close principle

Protection against change to the existing code and design at variation and evolution points

Meccanismo che mi permette di stabilire un legame, sottotipo se rispettiamo Liskov, su questo posso costruire Polimorphism.

Privilegiare composizione sull'ereditarieta' ESPANDERE

In statically typed languages like C++, one of the key mechanisms that supports abstraction and polymorphism is inheritance. It is by using inheritance that we can create derived classes that conform to the abstract polymorphic interfaces defined by pure virtual functions in abstract base classes.

Inheritance

Structured Query Language

Linguaggio dichiarativo.

DDL: comandi per creare strutture per l'ossatura della base di dati (CREATE TABLE, CREATE VIEW, ...)

DML: istruzioni (INSERT, DELETE, UPDATE, ...)

DQL: SELECT

DCL: comandi sul controllo

Funzioni / operatori / clausole

  • lower trasforma la stringa in lowercase
  • like
  • ilike insensitive like
  • trim rimuove gli spazi prima e dopo
  • ltrim spazi a sinistra
  • rtrim spazi a destra
  • between ad esempio ... vote BETWEEN 1 and 10;
  • in ad esempio ... lower(genre) in ('drama', 'thriller', 'crime')
  • distinct
    • ad esempio SELECT DISTINCT movie FROM imdb.genre (elimina i duplicati, rendendo il risultato "come fosse in algebra relazionale")
    • SELECT DISTINCT movie, genre FROM imdb.genre perché DISTINCT lavora sull'intera clausola SELECT
  • order by ordina ciò che restituiamo, e' l'ultima cosa che viene fatta; si può specificare DESC per più attributi in base alle necessita'
    Si puo' fare ORDER BY 2 per riferirsi alla colonna 2
  • extract permette di prendere parti di una data come ad esempio WHERE extract(YEAR FROM birth_date) = '1971'
  • as e' l'equivalente di nell'algebra relazionale
  • :: e' l'operatore di cast SELECT extract(year) from birth_date)::char(4) from imdb.person
  • is null unico modo per confrontare con NULL
  • ALL da true solo se il predicato e' verificato per tutti i valori restituiti dalla sotto query ad esempio nella seguente ammettendo che la sub-query ritornasse più valori, allora un record per poter essere restituito dovrebbe essere maggiore di tutti questi valori
SELECT id, official_title, length
FROM imdb.movie
WHERE length > ALL (
	SELECT length
	FROM imdb.movie
	WHERE official_title = 'Inception'
);
  • ANY da true se il predicato e' verificato per almeno un valore restituito dalla sotto query
  • min/max (agg)
  • avg (agg)
  • sum (agg)
  • count (agg) cardinalità di una relazione
    • COUNT(*) conta tutte le righe
    • COUNT(attributo) conta le occorrenze in cui l'attributo non e' NULL, sempre bene contare su chiave esterna per essere sicuro che sia NOT NULL
  • group by (agg) partizionare i record in base ad un criterio
  • having filtra sul risultato del gruppo, quindi su operatori aggregati o sugli attributi del gruppo; come ottimizzazione e' meglio usare una WHERE prima di arrivare alla GROUP BY in modo da raggruppare su meno record, quindi e' sconsigliato usare la HAVING al suo posto

(agg) sono operatori di aggregamento, ignorano il NULL; vengono eseguiti alla fine; posso proiettare solo attributi aggregati o prodotti da un operatore di aggregamento.
Non si possono fare aggregati di aggregati.

La precedenza degli operatori logici viene valutata da sinistra verso destra, quindi

SELECT *
FROM imdb.movie
WHERE year '2010' OR length >= 60 AND length <= 120;

Prima valuta year '2010' OR length >= 60 e poi il risultato di questa in AND.

% segnaposto per una stringa di qualsiasi lunghezza
_ segnaposto per una stringa di lunghezza 1

Prodotto cartesiano

SELECT *
FROM imdb.movie, imdb.produced

Cardinalità totale e' uguale a cardinalità movie (1033) per cardinalità produced (1332).

Join

Sono una selezione sul prodotto cartesiano.

SELECT *
FROM imdb.movie m, imdb.produced p
WHERE m.id = p.movie

movie.id chiave primaria, produced.movie chiave esterna.
Cardinalità e' uguale a cardinalità di produced.

Sintassi alternativa

SELECT m.id, m.official_title
FROM imdb.movie m 
INNER join imdb.produced p ON m.id = p.movie
WHERE country = 'USA'

a JOIN b ON c restituisce i record di a e b nel prodotto cartesiano che soddisfano c.

Eventuali WHERE vengono applicate dopo la JOIN!

Join esterno (outer join)

Aggiunge al join eventuali record che non hanno alcuna corrispondenza nella tabella di destra (nel caso di LEFT JOIN).
Prima fa un INNER JOIN applicando le clausole ON, poi aggiunge i record spuri (che sono quelli della tabella "dall'altro lato" per cui non e' stato trovato un corrispondente).

Ad esempio per la richiesta "selezionare paesi nei quali non sono prodotti film":

SELECT c.iso3
FROM country c
LEFT JOIN produced p ON c.iso3 = p.country
WHERE p.country IS null

La cardinalità quindi include anche i record senza corrispondenza.

FULL JOIN combina LEFT e RIGHT.

Viste

Oggetti derivati formati a partire dai dati nelle tabelle, per offrire delle proiezioni dei dati tipicamente appartenenti a tabelle diverse.

CREATE VIEW movie_person as (
	SELECT *
	FROM movie 
	INNER JOIN crew on movie.id crew.movie
	INNER JOIN person on person.id = crew.person
)

A questo punto trovare le persone di inception diventa quanto segue.
Prima viene eseguita movie_person e poi viene eseguita la seconda query (nessuna duplicazione dei dati).

SELECT *
FROM movie_person
WHERE official_title ilike 'inception' AND p_role 'actor'

Explain

Controllare i costi di una query, mostra l'albero di esecuzione della query.
Si parte a leggere dalle foglie.

EXPLAIN ANALYZE ...

Query correlata

Molto inefficiente
TODO espandere

Query ricorsive

La tabella sim descrive similarità tra pellicole, la colonna cause spiega il motivo di questa similarità.

  • Data una pellicola specifica suggerire le pellicole simili

Sono interessato alla pellicola 0013444, le sono simili quelle che rispondono a

SELECT movie2 FROM sim WHERE movie1 = '0013444'

Ma sono simili a 0013444 anche le pellicole simili a quelle restituite dalla query precedente, cioè se 0018756 e' restituito dalla query precedente, anche le query simili a 0018756 sono indirettamente simili a 0013444.

Magari vorrei fermarmi ad una "distanza 3". Posso interpretare le pellicole come nodi, un arco e' presente se c'e' una relazione di somiglianza tra le due pellicole.

Esempio base di query ricorsiva

WITH RECURSIVE t(n) AS (
	SELECT 1 
	UNION all 
	SELECT n+1 FROM t)
SELECT n FROM t

t(n) specifica che n e' il nome di cosa la query restituisce.
SELECT 1 e' il passo base, SELECT n+1 FROM t e' il passo ricorsivo.

Tutte le query ricorsive quindi seguono questo schema, con la UNION tra i due passi.

UNION ALL rimuove duplicati, UNION li tiene.

Il problema di questa query e' che non si ferma mai, quindi ne limitiamo l'esecuzione con la WHERE.


WITH RECURSIVE t(n) AS (
	SELECT 1 
	UNION all 
	SELECT n+1 FROM t WHERE n < 10)
SELECT n FROM t

La differenza tra UNION e UNION ALL e' nella gestione dei duplicati, la seconda gestisce anche i duplicati.

Altro esempio

Supponiamo di essere interessati ad una organizzazione dei generi che sia gerarchica.

CREATE TABLE genre_taxonomy {
  genre_name VARCHAR PRIMARY KEY,
  genre_parent VARCHAR
}
ALTER TABLE genre_taxonomy 
ADD CONSTRAINT parent_fk FOREIGN KEY (genre_parent)
REFERENCES genre_taxonomy(genre_name)

Serve ALTER TABLE perché e' un riferimento alla tabella stessa, quindi va prima creata la tabella, e poi si può introdurre la foreign key.

Relazione 1 a N:

  • 1 perché preso un sotto-genere ha sempre e solo 1 padre
  • N perché preso un sopra-genere può avere N figli
- thriller
	- noir
		- poliziesco
			- spionaggio
			- cronaca nera
	- splatter

Restituire i generi di 'poliziesco'

WITH RECURSIVE search_parent(the_genre, parent_genre) AS (
	SELECT genre_name, genre_parent
	FROM genre_taxonomy
	WHERE genre_name = 'poliziesco'
	union
	SELECT sp.the_genre, gt.genre_parent -- nota gli stessi nomi dei parametri
	FROM search_parent sp 
	JOIN genre_taxonomy gt ON sp.genre_parent = gt.genre_name
)

SELECT * FROM search_parent

Si ferma grazie al JOIN che non produce una riga quando trova un NULL nella sua condizione di ON.

Restituire i primi due generi di 'poliziesco', quindi mi fermo "prima di arrivare in cima"

Aggiungo un parametro distance

WITH RECURSIVE search_parent(the_genre, parent_genre, distance) AS (
	SELECT genre_name, genre_parent, 1
	FROM genre_taxonomy
	WHERE genre_name = 'poliziesco'
	union
	SELECT sp.the_genre, gt.genre_parent, sp.distance + 1
	FROM search_parent sp 
	JOIN genre_taxonomy gt ON sp.genre_parent = gt.genre_name
	WHERE distance < 1
)

SELECT * FROM search_parent

Riprendendo sulle pellicole simili

WITH RECURSIVE search_sim(movie, t_movie, distance) AS(
	SELECT movie1, movie2, 1
	FROM sim
	WHERE movie1 = '0013444'
	UNION
	SELECT ss.movie, si.movie2, distance + 1
	FROM search_sim ss 
	JOIN sim si ON ss.s_movie = si.movie1
	WHERE distance < 3
)

Esercizi

IMDB Schema

  • Selezionare il titolo delle pellicole del 2010
SELECT official_title
FROM imdb.movie 
WHERE year = '2010';
  • Cercare pellicole che hanno "murder" nel titolo
SELECT *
FROM imdb.movie
WHERE official_title LIKE '%murder%'
  • Trovare le pellicole prodotte in due paesi diversi
SELECT *
FROM imdb.produced p1, imdg.produced p2
WHERE p1.movie = p2.movie AND p1.country <> p2.country

Pero' in questo caso ottengo duplicati, quindi dovrei rimuoverli, posso risolvere usando il <

SELECT p1.movie, p1.country as country1, p2.country as country2
FROM imdb.produced p1, imdg.produced p2
WHERE p1.movie = p2.movie AND p1.country < p2.country
  • Persone decedute in un paese diverso da quello di nascita
SELECT *
FROM location l1, location l2
WHERE l1.person = l2.person AND
	l1.d_role <> l2.d_role AND l1.country <> l2.country

Ma se la lascio cosi ha duplicati, per rimuoverli uso

SELECT *
FROM location l1, location l2
WHERE l1.person = l2.person AND
	l1.d_role = 'B' AND l2.d_role = 'D' AND
	l1.country <> l2.country
  • Trovare le pellicole che non hanno materiali
SELECT movie.id
FROM movie

EXCEPT

SELECT DISTINCT material.movie
FROM material
  • Trovare le pellicole che sono prodotte in ITA e USA

La seguente e' sbagliata! Scorre la tabella produced cercando un record che contemporaneamente abbia country 'ITA' e 'USA'

SELECT *
FROM produced 
WHERE country = 'ITA' AND country = 'USA'

Viene semplicissimo con l'intersezione occhio a non usare * perché in un caso filtriamo country con ITA e nell'altro con USA quindi saranno sempre diversi, quindi mai intersecabili

SELECT movie FROM produced WHERE country = 'ITA'
INTERSECT
SELECT movie FROM produced WHERE country = 'USA'
  • Trovare i titoli delle pellicole prodotte in ITA e USA
SELECT id, official_title
FROM produced JOIN movie ON movie = id 
WHERE country = 'ITA'
INTERSECT
SELECT id, official_title
FROM produced JOIN movie ON movie = id 
WHERE country = 'USA'

Metto anche id nella SELECT per avere una intersezione solo quando anche l'id (che e' chiave) e' uguale, altrimenti potrei avere duplicati.

Posso farlo anche con una CTE .
Common Table Expressions

WITH mp AS (
	SELECT * FROM produced INNER JOIN movie ON movie = id
)
SELECT id, official_title FROM mp WHERE country = 'ITA'
INTERSECT
SELECT id, official_title FROM mp WHERE country = 'USA'

Il beneficio e' creare un oggetto in memoria, temporaneo.
Utile quando ho bisogno di diversi componenti.

Oppure ancora

SELECT id, official_title FROM movie WHERE  id IN (
	SELECT movie FROM produced WHERE country = 'ITA'
	INTERSECT
	SELECT movie FROM produced WHERE country = 'USA'
)

Oppure ancora con self join (che e' anche un razzo)

SELECT usa.movie
FROM produced usa 
JOIN produced ita ON usa.movie = ita.movie
JOIN movie ON usa.movie = id
WHERE usa.country = 'USA' AND ita.country = 'ITA'
  • Trovare titolo dei film con durata superiore alla durata di inception

La seguente non va bene senza ANY perché potrei avere più risultati dalla inner query

SELECT id, official_title, length
FROM imdb.movie
WHERE length > ANY (
	SELECT LENGTH
	FROM imdb.movie
	WHERE official_title = 'Inception'
)

O anche

SELECT DISTINCT m1.id, m1.official_title
FROM movie m1 JOIN movie m2
ON m1.length > m2.length
WHERE m2.official_title = 'Inception'

TODO e' possibile usare SELF JOIN per emulare il comportamento della ALL?

  • Trovare le pellicole prodotte solo in Italia
SELECT p.movie
FROM produced p
WHERE p.country = 'ITA' AND movie NOT IN (
	SELECT movie
	FROM produced
	WHERE country <> 'ITA'
)

Oppure con la sottrazione

SELECT movie FROM produced WHERE country = 'ITA'
EXCEPT 
SELECT movie FROM produced WHERE country <> 'ITA'

Oppure con join esterno

WITH itamovies AS (
	SELECT p.movie
	FROM produced p
	WHERE p.country = 'ITA'
),
nonitamovies AS (
	SELECT movie 
	FROM produced
	WHERE country <> 'ITA'
)
SELECT *
FROM itamovies LEFT JOIN nonitamovies ON itamovies.movie = nonitamovies.movie
WHERE nonitamovies.movie IS NULL

Oppure senza WITH

SELECT DISTINCT itamovies.*
FROM produced itamovies
LEFT JOIN produced nonitamovies ON itamovies.movie = nonitamovies.movie AND nonitamovies.country <> 'ITA'
WHERE itamovies.country = 'ITA' AND nonitamovies.movie IS NULL
  • Trovare il titolo di tutti i film con relativi generi

La seguente non troverebbe i film che non hanno genere

SELECT id, official_title, genre
FROM movie 
JOIN genre ON movie.id = genre.movie

quindi

SELECT id, official_title, genre
FROM movie
LEFT JOIN genre ON movie.id = genre.movie
  • Per ogni persona mostrare il nome e il country dove e' deceduto, incluse le persone per le quali non abbiamo un country di decesso
SELECT id, given_name, country
FROM person 
LEFT JOIN location ON person.id = location.person
WHERE d_role = 'D'

La WHERE viene valutata sull'effetto del JOIN, quindi lo annulla, perché per i record spuri d_role sarebbe NULL, quindi la query non e' corretta perché non aggiunge i NULL.

Possibile soluzione in cui filtriamo prima

WITH deaths AS (
	SELECT *
	FROM location l
	WHERE d_role = 'D'
)
SELECT id, given_name, country
FROM person 
LEFT JOIN deaths ON person.id = deaths.person

Oppure imponiamo il filtro nel momento del join

SELECT id, given_name, country
FROM person 
LEFT JOIN location ON person.id = location.person AND d_role = 'D'
  • Trovare coppie di pellicole che non hanno generi in comune

"Da tutte le possibili combinazioni, togliamo le pellicole che hanno un genere in comune"

SELECT DISTINCT g1.movie, g2.movie
FROM genre g1 JOIN genre g2 ON g1.movie > g2.movie
except
SELECT DISTINCT g1.movie, g2.movie
FROM genre g1 JOIN genre g2 ON g1.movie > g2.movie
WHERE g1.genre = g2.genre

oppure (notare l'uso di c nella Query correlata)

WITH couples as (
	SELECT DISTINCT g1.movie AS movie1, g2.movie AS movie2
	FROM genre g1 JOIN genre g2 ON g1.movie > g2.movie
)
SELECT * 
FROM couples c
WHERE NOT EXISTS (
	SELECT * ;; irrilevante cosa abbiamo qui
	FROM genre g1, genre g2
	WHERE g1.movie = c.movie1 AND g1.movie = c.movie2 AND g1.genre = g2.genre
)
  • Trovare i film che non sono stati distribuiti nei paesi nei quali sono stati prodotti

Tabelle interessate: produced e released

Un movie m viene inserito nel risultato se non esiste un record della tabella produced p per il quale esiste un record di release relativo allo stesso movie e paese di p

SELECT id, official_title
FROM movie m
WHERE NOT EXISTS (
	SELECT *
	FROM produced p
	WHERE p.movie = m.id AND EXISTS (
		SELECT *
		FROM released r
		WHERE r.movie = m.id AND p.country = r.country
	)
)

Utile quando devo contemporaneamente far valere più vincoli. Con gli operatori insiemistici non me la cavo perché devo controllare le condizioni su ogni singolo record. TODO ESPANDERE Con esempio e spiegazione

Oppure

SELECT id, official_title
FROM movie m
WHERE NOT EXISTS (
	SELECT *
	FROM produced p JOIN released r ON p.movie = r.movie AND 
		p.country = r.country
	WHERE p.movie = m.id
)
  • Trovare il film di durata maggiore
SELECT max(length)
FROM movie
  • Trovare il film di durata maggiore e restituire il titolo

La seguente va in errore perché nell'aggregare perdono il riferimento alla tupla che contiene quel valore, inoltre potrei avere più di un record con quel valore, quindi devo aggregare tutto ciò che voglio selezionare

SELECT id, official_title, max(length) FROM movie

Un modo per farlo e'

SELECT * FROM movie WHERE length = (
	SELECT MAX(length) as durata_massima FROM movie
)

Oppure anche

WITH mmax AS (
	SELECT MAX(length) as durata_massima FROM movie
)
SELECT m.* 
FROM movie JOIN mmax ON m.length = mmax.durata_massima
  • Trovare il numero di pellicole per le quali e' noto l'anno di produzione

Non serve specificare WHERE year IS NOT NULL (vedi descrizione COUNT)

SELECT count(year) FROM movie

Invece cosi serve

SELECT COUNT(*) FROM MOVIE WHERE year IS NOT NULL
  • Trovare il numero di titoli diversi delle pellicole
SELECT COUNT(DISTINCT official_title) FROM movie
  • Trovare la durata media dei film del 2010
SELECT sum(length) / count(length)
FROM movie
WHERE year = '2010'
  • Trovare il numero di pellicole per ogni anno disponibile

Conteggia in base ad ogni gruppo trovato dalla GROUP BY

SELECT year, COUNT(*)
FROM movie
GROUP BY year
  • Trovare per ciascun film il numero di persone coinvolte per ciascun ruolo
SELECT COUNT(*)
FROM crew
GROUP BY movie, c.p_role
  • Trovare il numero di valutazioni per ogni film

La seguente pero' non tiene conto delle pellicole senza valutazioni, perché dentro rating ho solo pellicole già valutate

SELECT movie, COUNT(*) 
FROM rating
GROUP BY movie

La seguente produce il risultato atteso ma e' convoluta

SELECT movie, COUNT(*)
FROM ratingGROUP BY movie
UNION
(
	SELECT id, 0
	FROM movie
	EXCEPT 
	SELECT movie, 0
	FROM rating
)

Nella seguente occhio a non usare COUNT(*) perché altrimenti conterebbe le righe, invece usando COUNT(r.movie) conteggio quando la riga non e' spuria.

SELECT movie, COUNT(r.movie)
FROM movie m LEFT JOIN rating r ON m.id = r.movie
GROUP BY id
  • Trovare il miglior rating di ciascun film
SELECT movie, MAX(score / scale)
FROM movie m LEFT JOIN rating r on movie.id = rating.movie
GROUP BY id
ORDER BY 2 DESC
  • Trovare l'attore che ha recitato nel maggior numero di film

MAX(COUNT(*)) non si può fare.

La seguente e' sbagliata, perché potrebbero esserci più record con il valore massimo.
DISTINCT MOVIE per rimuovere attori con più di un ruolo nello stesso film.

SELECT person, COUNT(DISTINCT movie)
FROM crew
WHERE p_role = 'actor'
GROUP BY person
ORDER BY 2 DESC
LIMIT 1

Soluzione

WITH recitazioni AS (
	SELECT person, COUNT(DISTINCT movie) AS n_partecipazioni
	FROM crew
	WHERE p_role = 'actor'
	GROUP BY person
)
SELECT id, given_name, n_partecipazioni
FROM person p JOIN recitazioni r ON p.id = r.person
WHERE n_partecipazioni = (
	SELECT MAX(n_partecipazioni) AS max_partecipazioni
	FROM recitazioni)

Soluzione alternativa

SELECT person, COUNT(DISTINCT movie) AS n_partecipazioni
FROM crew
WHERE p_role = 'actor'
GROUP BY person
HAVING COUNT(DISTINCT movie) >= ALL (
	SELECT COUNT(DISTINCT movie)
	FROM crew
	WHERE p_role = 'actor'
	GROUP BY person
)
  • Trovare i film con cast più numeroso della media dei film del medesimo genere
WITH movie_cast AS (
	SELECT movie, COUNT(DISTINCT person) AS n_person
	FROM crew
	WHERE p_role in ('actor', 'director')
	GROUP BY movie
),
avg_genre AS (
	SELECT genre, AVG(n_person) as avg_cast
	FROM movie m LEFT 
	LEFT JOIN genre g ON m.id = g.movie 
	LEFT JOIN movie_cast mc ON m.id = mc.movie
	GROUP BY genre
)
SELECT m.id, official_title, g.genre, n_person
FROM movie m
LEFT JOIN genre g ON m.id = g.movie
LEFT JOIN movie_cast mc ON m.id = mc.movie
WHERE mc.n_person > (
	SELECT avg_cast 
	FROM avg_genre 
	WHERE g.genre = avg_genre.genre 
)
  • Trovare le persone che hanno recitato in tutti i film di genere crime (divisione)

Tabelle utili

In algebra e'

SELECT id, given_name
FROM person p
WHERE NOT EXISTS (
	SELECT *
	FROM genre g
	WHERE g.genre = 'Crime' AND NOT EXISTS (
		SELECT *
		FROM crew c
		WHERE p_role = 'actor' AND p.id = c.person AND g.movie = crew.movie
	)
)
  • Restituire le persone che hanno svolto più di un ruolo all'interno dello stesso team

Questa prima query e' in grado di dire quali ruoli e la persona ha svolto e in che film...

SELECT DISTINCT c1.person, c1.p_role, c2.p_role, c1.movie
FROM crew c1
JOIN crew c2 ON c1.person = c2.person
WHERE c1.p_role <> c2.p_role AND c1.movie = c2.movie
ORDER BY c1.person

...mentre questa query e' in grado di dire che la data person ha svolto più ruoli all'interno del film, ma non sa dire quali per via dell'aggregazione che fa perdere informazione; per contro questa query e' più performante della precedente

SELECT person, movie
FROM crew
GROUP BY movie, person
HAVING COUNT(DISTINCT p_role) > 1;
  • Trovare i film che hanno un cast più numeroso della media

ANY e' opzionale nel caso di un solo record ritornato

WITH cast_crew_num AS (
	SELECT movie, COUNT(*)
	FROM crew
	GROUP BY movie
),
mean AS (
	SELECT AVG(count) AS m
	FROM cast_crew_num
)

SELECT *, (SELECT m FROM mean)
FROM cast_crew_num
WHERE count > (SELECT m FROM mean)
SQL

Dati strutturati

Sfide nella gestione dei dati:

  • duplicazione
  • violazione dell'integrità
  • errori di inserimento
  • incapacità di controllare il tipo di dato
  • difficoltà di collegamento tra file
  • controllo dell'accesso
  • valori nulli - alcuni valori non possono mancare

Tutti questi vengono gestiti dai DBMS.

Vincoli - leggi che voglio applicare per mantenere l'integrità dei dati

In realtà e' deleterio perché per come e' strutturata la tabella non serve.

La tabella e' comunque utile perché consente di sapere rapidamente quali sono i generi presenti nella base dati

La struttura delle tabelle deve cambiare solo quando cambiano i requisiti.
Rigidita' dei database.

Case study: film associati a recensioni

Se c'e' un rating allora vuol dire che c'e' uno e un solo film, per via del fatto che c'e' come chiave esterna.

Caratteristiche

DDL - Data Definition Language - lavora sullo schema
DML - Data Manipulation Language - lavora sui dati

Indipendenza - astrazione di come i dati vengono memorizzati su disco, che ci consente di disinteressarci di come sono fisicamente distribuiti

DQL - Data Query Language - ogni utente ha una vista dei dati che e' di interesse per quello specifico utente

Controllo della concorrenza - condivisione magari sugli stessi dati o addirittura sullo stesso record (lock, transazioni)

DCL - Data Control Language - controllo degli accessi

Database sono i dati, DBMS e' il software.

Definizioni

DBMS - sistema software in grado di gestire collezioni di dati che siano grandi, condivise, persistenti assicurando la loro affidabilità e privatezza, deve essere efficiente ed efficace
Una base di dati (o database) e' una collezione di dati gestita da un DBMS

Architettura a tre livelli

Modello relazionale

Definizione di tabella
Basato sul concetto di relazione matematica , intesa come sottoinsieme del prodotto cartesiano fra due o più insiemi di dati detti domini

Dove , sono domini: cioè possibili valori che può assumere un attributo che usa questo dominio.

Posso definire una relazione

che avrà elementi (), e che sara' espressa come

Posso scrivere

dove nella prima riga ho la relazione con i domini, nella seconda riga ho un esempio di tupla, dove , , . Notare come non serve dare un nome a , ma e' sicuramente utile farlo, quindi con voglio puntare al valore nella tupla .

Schema di una base di dati

Insiemi degli schemi di tutte le relazioni che lo costituiscono, ogni relazione ha poi il suo schema:

Dove fino ad arrivare ad sono gli schema (o insiemi di attributi).

Lo schema non cambia se cambiano i requisiti, l'istanza si e anche spesso
Istanza della base di dati e' l'insieme dei record nelle relazioni.


Estensionale ed intensionale
La parte estensionale di una tabella sono i suoi dati. La parte intensionale e' il suo schema.

Per ogni tabella esiste sempre un identificatore, al peggio e' composto da tutti i valori degli attributi che definiscono la tabella.

Grado di relazione: quanti attributi sono definiti sulla relazione
Cardinalità di una relazione: quante tuple ci sono nella relazione

Vincoli

Caratteristiche che i dati devono avere per poter essere ammessi.
Predicato il cui valore di verità e' verificato all'inserimento di un possibile record nella relazione.

  • intra-relazionali: all'interno della stessa relazione
  • inter-relazionali: tra diverse relazioni

Avere un tipo di valore DATE già fornisce vincoli su come deve presentarsi il dato.

Vincolo di NULL e NOT NULL

Consentire l'inserimento di NULL e' il default.

Ad esempio

Potrebbe avere come vincoli di integrità

Vincoli di chiave

Valori che referenziano valori identificativi referenziati altrove.

Superchiave

Una qualsiasi combinazione di attributi che garantisce l'univocità dei valori nelle ennuple della relazione.
Data superchiave della relazione , non esistono due tuple , in per le quali .

La seguente relazione

avrebbe come superchiavi

  • ogni combinazione di attributi che contenga
  • (dipende da quali situazioni vogliamo tollerare, "ci possono essere due film con la stessa trama?")
  • (dipende da quali situazioni vogliamo tollerare, "ci possono essere due film con lo stesso titolo e la stessa trama?")

Chiave

Una chiave e' una superchiave minimale: non posso toglierle alcun attributo senza che sia una chiave.
e' un insieme di attributi superchiave di , dato un qualunque insieme , e' chiave se può esistere .

Non posso togliere attributi alla chiave mantenendone la caratteristica di chiave, ad esempio a non posso togliere , o , o , senza perdere univocità.

Tutte le combinazioni che contengono sono superchiavi, ma non sono chiavi perché posso togliere qualsiasi attributo, dal momento che da solo basta ad identificare una tupla.

Chiave primaria

Vincolo di entity integrity; e' una chiave sulla quale non sono possibili valori NULL. Ogni relazione ha una e una sola chiave primaria.

Integrità referenziale

Metto in relazione dati appartenenti ad entità diverse. Permette l'operazione di JOIN

e , la prima e' la relazione che referenzia, la seconda e' quella referenziata

contiene attributo o insieme di attributi detto chiave esterna
contiene attributo o insieme di attributi che e' chiave per

Possibili problemi quando si fa un INSERT o UPDATE:

  • chiavi
  • vincoli integrità
  • vincoli dominio - inserisco una stringa dove mi aspettavo un numero
  • valori null

Quando si fa una DELETE su una tabella referenziata si può avere un problema solo se esistono record nella tabella referenziante; NO ACTION.
Politiche NO ACTION e CASCADE servono a mantenere l'integrità referenziale.

Un film viene proiettato in un cinema in un certo giorno e orario:

con:

  • chiave
  • foreign key
  • referenziano e potrei scrivere in SQL FOREIGN KEY (c_name, c_city) REFERENCES cinema(name, city) ON UPDATE CASCADE ON DELETE NO ACTION; per evitare la chiave esterna composta, si può considerare l'uso di una chiave atomica

Se una tabella non e' referenziata da altre tabelle non serve un .

Data

id firstname lastname father
1 Mario Montanelli [NULL]
2 Stefano Montanelli 1

e' chiave esterna, sebbene si riferisca alla stessa tabella: FOREIGN KEY (father) REFERENCES person(id); inoltre la chiave esterna può essere NULL.


VECCHIO file


Relational model

Mathematical relations between sets (domains) via a Cartesian product.
Data is organized in rows, all rows are different from each other (since they are the result of a Cartesian product).

Keep the schema as fixed as possible, so that applications using it do not need to change accordingly.

Given and the Cartesian product is .
A relation is a subset of that product such that .

NULL

Absence of a value in the domain.
We can't use it to compare values, an attribute which could be could not be compared with, for example, to understand if it comes before or after. We can't even say if they're different or not (in a way).

Open-world semantic

Everything that does not falsify a predicate it's true.

Closed-world semantic

Only something that verify a predicate it's true.

Constraints

A predicate that for each instance could return true or false.

In a table

  • domain constraints - for example
  • row constraints - express conditions on attributes on a row, domain constraints are row constraints that operate on a single attribute
  • key constraints

Functional dependency

A dependency  means that the values of  are determined by the values of .

"Which are the minimum attributes I need to determine that attribute?"

This allows to determine which attributes go in which tables.

Of course we could only recognize functional dependencies if we know the domain of the application, if we don't know anything it's very hard to do a good job in identifying them.

Key question to ask ourself to see if there's a functional dependency: "left side of this hypothetical dependency identifies precisely the right side?" if not then there isn't a functional dependency.

Normal forms

We want to avoid redundancy in the information.

A database is well formed if it has 1NF, 2NF, 3NF.
As a rule of thumb:

  • we want lots of writes more normalization
  • we want lots of reads less normalization

1NF

Only atomic attributes.
We don't have a relational database if we don't have this NF.

2NF

Depends on 1NF.

Every non-key attribute must depend on the primary key, as a whole, and not just on a subset of it.
Normalization: put the subset of the key and the attribute in a new table.

If the key is a single attribute then we always have 2NF.

3NF

Depends on 2NF.

Transitive dependency: is transitive if a set of attributes (not key and not subset of a key) exists such that and .
2NF and no non-prime attribute in depends in a transitive way on the primary key.

In other words: we don't want two non-key attributes on the left AND on the right of a functional dependency. They all have to be on the left side.

BCNF

All non prime attributes must depend on a super-key.

The key in the decomposed relations must be a super-key in the original relation.

Using normal forms to decompose relations

Goal is to start from a big universal relation and finish to a scheme with multiple relations, which respect the normal forms.

There is a procedure to follow, which has 3 rules:

  • preserve attributes
  • preserve functional dependencies
  • lossless joins - we don't want joins to that create data that was not present in the original relations

The algorithm is:

  1. define functional dependencies
  2. minimize functional dependencies
  3. for each functional dependency , create a new relation where we have and all dependencies on the right side that have on the left side
  4. attributes left out go in a relation of their own

Normalization

Anomalies and redundancies arise when we have functional dependencies like , where is not a superkey (a subset of the key).
To avoid redundancies and anomalies, we have to put our relation in BCNF.

Database intro

Come costruire un database, come progettarlo.

Attività di progettazione

Modello ER

Entità sono rettangoli
Associazioni sono rombi, più importante il significato rispetto al suo nome, cioè quali entità associa
Per ogni relazione ci sono due cardinalità

Esempio

Si modelli un diagramma ER che descrive una realtà ospedaliera in cui i medici curano i pazienti

Schema ER di una realtà ospedaliera

  • quali sono le entità che voglio rappresentare? Le chiamo al singolare, sto descrivendo un singolo oggetto di quel tipo. Uso il rettangolo per comunicare che sto mostrando una entità
  • metto solamente quegli attributi che sono presenti nella realtà che sto modellando, se parlo di pazienti non avrò id, ma avrò tessera_sanitaria
  • porre attenzione agli attributi composti come ad esempio residenza: non voglio concatenare tutto in una stringa
  • esprimo la cardinalità con (x, y) dove x e' la cardinalità minima, y la cardinalità massima
    • la cardinalità minima non può essere N
    • la cardinalità minima e' sempre meno della massima
    • la cardinalità va espressa per entrambe le direzioni della relazione
    • la cardinalità della relazione la si capisce guardando la cardinalità massima sui due lati della relazione (esempio se avessi (0,N) e (1,N) sarebbe una cardinalità molti a molti)

Altri esempi

Seguono alcuni esempi con richieste differenti

Esempio Medico - Paziente Paziente - Medico Tipo
Un medico cura non più di 1000 pazienti, e un paziente non può avere più di 10 medici curanti (0, 1000) (1, 10) N N
Un medico cura almeno 10 e non più di 1000 pazienti, e un paziente non può avere più di un medico curante (10, 1000) (1, 1) 1 N

In una N a N una associazione viene tradotta con una tabella con un identificativo per parte.
In una 1 a N la chiave esterna va nell'entità che ha 1 come cardinalità massima.

"Ogni medico ha un supervisore che e' un altro medico", produrra' due modi di leggere l'associazione:

  • ha_supervisore (1, 1)
  • e'_supervisore (0, N)

Ed e' una 1 a N.

"I medici lavorano in reparti all'interno di ospedali, ogni reparto ha un primario scelto tra i medici che vi lavorano"

Mettere primario come attributo di Reparto e' un errore, perché lo identificherei con la sua matricola, e quindi sarebbe un Medico, quindi modello come una associazione.

Entità debole

Reparto da solo non e' in grado di descriversi univocamente.

La cardinalità minima e' sempre (1, 1).

Esempio di entità debole

TODO prendi da appunti di lezione gli altri schemi, ad esempio dove msotra attributi sulle associazioni

TODO mettere rimando al vincolo extra schema: non si puo' gestire con un elemento dello schema, verrebbe gestito da trigger

Problema della storicizzazione dei dati

Si tratta di scegliere se far si che la base di dati mostri la situazione attuale, o tenga uno storico di come si e' evoluta la situazione nel tempo.

TODO vedi l'esempio dagli appunti del prof su come cambia lo schema ER

Gerarchie di generalizzazione

Vincoli che riguardano delle specifiche entità.

"Negli ospedali lavorano diverse figure professionali, tra le quali distinguiamo: medici, infermieri, dirigenti, personale amministrativo"

Obiettivo e' avere una entità che generalizzi le diverse figure.

La gerarchia ha due caratteristiche, che vanno sempre specificate (una per coppia):

  • totalità e parzialità "a quali sotto classi può appartenere?"
    • totale: un elemento appartiene ad almeno una delle sottoclassi (l'unione delle sottoclassi ricostruisce la superclasse)
    • parziale: esistono individui non appartenente ad alcuna delle sottoclassi
  • esclusività e sovrapponibilità
    • esclusività: un individuo e' collocabile solo in una delle sottoclassi, intersezione tra le sottocategorie e'
    • sovrapponibile: un individuo può appartenere a più sottoclassi

Le sottoclassi possono non avere identificatore, nel caso questo sia presente nella superclasse.

Quando ci sono attributi che sono in comune tra le relazioni e' bene chiedersi se si e' in presenza di una possibile gerarchia di generalizzazione.

Esempi

"Si sappia che un Dipendente può essere Medico, Dirigente, Infermiere, Amministrativo e Custode"

TODO aggiungi questo esempio

TODO prendi ER dagli appunti del prof

TODO prova a fare lo schema sui dati immobiliari proposto dal prof e dopo guarda la soluzione

Esercizio su caso d'uso Events

La base di dati dovrà memorizzare:

  • dati relativi a clienti distinti in persone fisiche e giuridiche
  • dati relativi agli eventi organizzati, tra i quali si distinguono banchetti e convegni
  • dati relativi ai partecipanti agli eventi con eventuali intolleranze (si tenga presente che non è possibile inserire dati anagrafici dei partecipanti se non il nome e il cognome). Si memorizzino gli accompagnatori (il partecipante A può essere accompagnato da B e C) sapendo che un partecipante può avere più accompagnatori, ma può accompagnare solo un partecipante.
  • dati relativi ai ristoranti che gestiscono i banchetti. Tra i ristoranti si distinguono quelli caratteristici
  • dati relativi ai menu serviti ai banchetti. Un menu è un insieme di portate classificate come antipasto, primo, secondo, dessert. Tra le portate si distinguono le specialità che possono essere offerte solo in ristoranti caratteristici. Il costo di un menu è la somma dei costi delle portate incluse nel menu

TODO rifallo da solo e controlla sia rispetto alla versione dell'anno scorso che rispetto a quella di questo anno

Come abbiamo proceduto a lezione:

  • per ogni entità ci chiediamo quali sono gli identificatori, eventualmente pensando se siamo in presenza di una entità debole
  • identificato cliente, come gerarchia di persona fisica e persona giuridica (T, E)
  • identificato evento, come gerarchi di banchetto e convegno (P, S)
  • pensiamo a come collegare le entità con le associazioni
  • ogni volta che ottengo "gruppi" di entità mi chiedo anche li come collegarli con le associazioni

Progettazione logica

Da modello concettuale a modello logico.

TODO integra con il diagramma che avevi fatto e mettilo qua
TODO integra con gli esempi nel materiale del prof su Ariel

Partiamo da questo

Schema ER di una realtà ospedaliera

Chiavi primarie sono sottolineate con riga continua. TODO metterlo in latex
Attributi opzionali sono denotati da un

  • Parto da una entità -

  • Scrivo i suoi attributi -

  • Prossima entità -

  • Prossima entità (focus solo sugli attributi interessanti) -
    Scegliendo come unica stringa allora renderò alcune query molto difficili da fare, dovrò fare il parsing, quindi dipende dal dominio; se invece voglio poter fare query allora li metto come attributi separati dell'entità
    Gestione attributi composti.
    Per un attributo multi-valore come la faccio diventare una entità. Che quindi diventa una associazione 1 a N, 1 perché l'attributo e' diventato entità debole.

    Quindi ottengo una nuova relazione -
    1 a N: metto l'identificatore nel lato 1 che ho nel lato N
    Con la seguente notazione specifico a quale chiave fa riferimento la chiave esterna:

  • vedo se ci sono associazioni rimanenti

  • parto da , che diventa una nuova tabella perché ho bisogno di mantenere quale e' associato a quale : , e quindi aggiungo le specifiche per la chiavi esterne

  • Prossima associazione : , e quindi aggiungo le specifiche per la chiavi esterne


Abbiamo sbagliato il modello concettuale, manca dentro , perché altrimenti non avrei potuto avere una visita tra lo stesso medico, lo stesso paziente, nello stesso ambulatorio, il che e' assurdo. L'attributo e' parte della chiave quando voglio come relazione storica.

non e' rappresentato come 0, come dovrebbe essere secondo la cardinalità, ma siccome fa parte della chiave deve esserci, quindi quando non c'e' semplicemente non appare in . E' con i LEFT JOIN che vado a pescare questi record quando mi serve.
In realtà sono sbagliate le (1,N) perché vorrebbe dire che ogni volta che aggiungo un devo subito assegnargli un il che può non essere vero.

Nel caso di entità deboli faccio prima l'entità a cui sono associate.

Per convenienza e' possibile mettere un dove oltre agli identificatori naturali uso un identificatore "utile", perché cosi ad esempio le JOIN non sono un delirio di ON a causa dei molteplici attributi coinvolti nella chiave.
E' una scelta fatta in fase di ristrutturazione.

Per una associazione con N entità devo prendere tutte le chiavi di ogni entità partecipante, per poi metterla nella nuova tabella.

Gestione gerarchie

TODO mettere immagini da appunti del prof

Accorpamento su entità padre (verso l'alto)

Si può sempre fare, con ogni tipo di gerarchia.
La gerarchia collassa in un'unica entità, che e' quella padre e:

  • si aggiunge un tipo enumerativo che identifica le figlie, nella gerarchia totale tipo e' obbligatorio, nella gerarchia parziale tipo e' opzionale; occhio che nelle sovrapposte il tipo deve considerare tutte le possibili combinazioni (può diventare poco maneggevole)
  • si aggiungono eventuali attributi delle entità figlie, che arrivano come opzionali

Eliminazione dell'entità padre (verso il basso)

Non e' praticabile con una gerarchia parziale.
La gerarchia viene spostata verso il basso, tutti gli attributi del padre vengono dati ai figli.
Se non sappiamo ad esempio se una persona e' fisica o giuridica dobbiamo fare una UNION per cercarla dappertutto.

Mantenimento di tutte le entità

La gerarchia viene scomposta in associazioni binarie ().
I figli sono sempre entità deboli rispetto al padre.

Database progettazione

Linguaggio di interrogazione procedurale che definisce le operazioni necessarie per estrarre dati da una o più relazioni di un database.

Qualunque operazione produce una relazione.

DQL, quindi non si modificano i dati, si interrogano solamente.

Principio fondamentale

Una tupla alla volta: non abbiamo possibilità di vedere tutti i dati assieme, ma solo una tupla alla volta, quindi le dobbiamo mettere a fianco una all'altra.

Operatori

Operatori unari:

  • proiezione
  • selezione
  • ridenominazione

Operatori binari insiemistici:

  • unione
  • intersezione
  • sottrazione

Operatori binari di join:

  • join
  • equi join
  • natural join

Insiemistici

Ricorda che le relazioni in un database sono insiemi, vedi Modello relazionale.

Precondizioni
Per poter fare unione, intersezione, differenza le relazioni devono avere pari grado, e attributo per attributo sono compatibili (le relazioni che partecipano non possono avere vincoli di dominio diversi).

Unione

Date e restituisce gli elementi appartenenti a o (presi una volta, non ci devono essere duplicati)

Produce il medesimo grado della relazione di partenza e cardinalità pari a cardinalità di a cui vanno sommati i record di (escludendo i duplicati, perché dal punto di vista matematico sono lo stesso individuo).

Il nome della colonna e' quello della relazione di sinistra.

Se avessi

  • in movie_a (003, 'shutter island', '2010', 138)
  • in movie_b (003, 'shutter island', '2010', 120)

E ne facessi l'unione otterrei entrambi i record perché sono due individui diversi.

Intersezione

Date e restituisce gli elementi appartenenti a e .

Produce il medesimo grado della relazione di partenza e cardinalità pari agli elementi in comune.

Differenza

Date e restituisce gli elementi appartenenti a e non a .
La differenza non e' una operazione simmetrica.

Selezione

Risponde alla domanda "Quali record della relazione voglio considerare?"

Accetta un predicato, quindi posso usare AND, OR, NOT; conserva il grado e la cardinalità e' minore o uguale a quella di partenza.

Esempio: voglio trovare le pellicole del 2010

Scorre i record uno a uno, quindi e' una valutazione locale, non globale, quindi non può essere usata per quei casi dove si vogliono ottenere valori relativi al globale.

Proiezione

Seleziona solo l'insieme di attributi che voglio mostrare nel risultato. Riduce il grado e la cardinalità e' uguale a quella di partenza.

E' possibile che applicando una proiezione si possano ottenere righe uguali, in questo caso record uguali collassano in uno solo.

Chiedersi sempre se e' il caso di ridurre gli attributi applicando una proiezione.

Prodotto cartesiano

A B
a1 b1
a2 b3
C D
c1 d1
c2 d2
c3 d3

produce una relazione avente come grado la somma dei gradi delle relazioni, e una cardinalità che e' il prodotto delle cardinalità delle relazioni

A B C D
a1 b1 c1 d1
a1 b1 c2 d2
a1 b1 c3 d3
a2 b2 c1 d1
a2 b2 c2 d2
a2 b2 c3 d3

Ridenominazione

Data se scrivo ottengo .

Join

join

Il prodotto cartesiano ritorna tutte le combinazioni, ma non tutte le righe concorrono al risultato di questo join, solo quelle dove la chiave primaria della relazione di sinistra e la chiave esterna della relazione di destra sono le stesse.

Quindi e' una selezione sul risultato del prodotto cartesiano. e' il predicato che rappresenta questa selezione.

Equi join

Join che hanno una uguaglianza come predicato.

Join naturale *

Equi join che considera l'eguaglianza degli attributi con il medesimo nome nelle due relazioni. Eventuali attributi con lo stesso nome collassano in uno solo.

Come si vede nell'ultima riga compare una volta sola.

Divisione

Vediamo questo operatore con un esempio

Permettere di evidenziare una interazione tra tuple di due tabelle: "vuole restituire quei record della tabella che sono in relazione con tutte le tuple della relazione ", nell'esempio la divisione restituirebbe quelle pellicole che sono state prodotte in tutti i paesi.

Viene definita come l'opposto al prodotto cartesiano.

Numeratore e denominatore
Al denominatore metto elementi che vogliamo siano in relazione con il soggetto di "tutti".
Al numeratore metto una relazione i cui attributi contengono gli attributi al denominatore, e che voglio portare nel risultato .

Dal punto di vista formale, date due relazioni e posso applicare la divisione se lo schema ha un insieme di attributi contenuto nello schema , con stesso dominio e stesso nome.

ha come schema la differenza tra lo schema di e lo schema di , quindi in questo caso e' definita su .

Nell'esempio vogliamo trovare un record che ha pari a , a parità di .

Altro esempio


| a | b |
| --- | --- |
| a1 | b1 |
| a2 | b1 |
| a1 | b2 |


| b |
| --- |
| b1 |
| b2 |
Dobbiamo trovare un valore di per il quale troviamo in un record di per cui quel valore di compare. darebbe


| a |
| --- |
| a1 |
La divisione e' utile quando non so a priori cosa ho a denominatore.

Ulteriore spiegazione

Date due relazioni e , identifica i valori in che possono essere combinati con tutti i valori in . E' l'inverso del prodotto cartesiano.

relational-algebra-cartesian-product.png
relational-algebra-division.png

relational-algebra-in-short.png

If then e .

Riguardo "all"

Per "trovare tutti gli X che Y", posso:

  • "trovare tutti gli X che non Y", e poi
  • "rimuovere da X cio' che ho appena trovato"

Per "trovare gli X che sono Y in tutti gli Z", posso:

  • "trovare tutte le possibili combinazioni" All
  • da All rimuovo i record che verificano la condizione, cosi ottengo Opposite
  • dai record esistenti rimuovo Opposite

Suggerimenti

Cercare di limitare quanto più possibile il grado di una relazione, in modo da fare che le relazioni si possano combinare più facilmente.

Esercizi con lo schema imdb

IMDB Schema

  • Trovare le pellicole del 2010 che non sono thriller

Procedura:

  • relazione : una relazione con grado 1 che contiene l'attributo id e i record dei movie prodotti nell'anno 2010
  • relazione : una relazione con grado 1 che contiene l'attributo delle pellicole thriller
  • quindi posso fare

Sto cercando l'assenza di qualcosa, quindi devo usare la sottrazione o un join esterno.

Usare la proiezione per ottenere relazioni confrontabili in termini di sottrazione
Siccome ha 3 attributi, e ne ha 2 queste due relazioni non sono confrontabili ne per numero ne per dominio . Anche se le colonne hanno nome diverso hanno lo stesso tipo di dato, quindi sono confrontabili.

  • Trovare le pellicole che sono di genere "thriller" o "crime"

Procedura:

  • trovo le pellicole di genere "thriller"

  • prendo l'id dalla relazione precedente

  • trovo le pellicole di genere "crime"

  • prendo l'id dalla relazione precedente

  • unisco le due relazioni ottenute

  • Trovare le pellicole che sono di genere "comedy" e "romantic"

Appartenere ad entrambi i generi vuol dire avere due record in cui c'e' il codice della pellicola e ognuno dei due "genre" voluti

Procedura:

  • trovo le pellicole di genere "comedy"
  • prendo l'id dalla relazione precedente
  • trovo le pellicole di genere "romantic"
  • prendo l'id dalla relazione precedente
  • prendo l'intersezione delle due relazioni ottenute

E' sbagliato perché il risultato e' simile a

movie_comedy

id genre
1 comedy
2 comedy

movie_romance

id genre
3 romance
1 romance

Che non produce il risultato sperato perché non ci sono record uguali su cui fare intersezione, quindi devo usare la proiezione:

Soluzioni alternative? No!

e' sbagliata perché prendo anche le righe che soddisfano una delle due.
e' sbagliata perché ogni riga viene valutata individualmente, ma nessun record può avere entrambi, quindi da sempre come risultato .

  • Trovare le persone che hanno interpretato come attore il personaggio 'Dexter'

In questo caso un record ha entrambi gli stessi attributi, quindi posso usare , differentemente rispetto a prima.

Posso usare l'atomizzazione delle selezioni per ottimizzare la query (l'operazione più selettiva va fatta per prima per poter dare alla seconda un set più piccolo possibile su cui lavorare)

  • Trovare il nome delle persone nate dopo il 2000 che recitano in film thriller

Procediamo per step

  • Guardando lo schema IMDB dato a lezione

  • Trovare il titolo delle pellicole con valutazione (rating) maggiore di 8

  • Trovare le pellicole thriller con valutazione sopra 8

  • Trovare il nome dei registi di film thriller

  • Trovare i film le cui recensioni sono sempre superiori a 8 (non immediato!)

  • Trovare le pellicole distribuite (released) sia in USA sia in FRA

  • Trovare le pellicole che non sono prodotte in GBR

Questa e' sbagliata, perché potrei avere una situazione di questo tipo

movie country
m1 FRA
m1 GBR
m2 FRA

Non ci si può accontentare della selezione per via della presenza di , quindi devo fare la sottrazione.

  • Trovare titolo e anno dei film che sono thriller, drama, e action

Il risultato e' definito sulle colonne , , .

Funzionerebbe anche con l'intersezione in questo caso, ma il vantaggio della divisione e' che non e' necessario specificare a priori come e' composto il divisore.

Esercizi usando uno schema simile a quello dell'esame

  • Trovare il nome dei politici che non hanno governato città con più' di 500.000 abitanti

A: seleziono città con più di 500k abitanti
B: seleziono da govern i politici che hanno governato le città in A
tolgo da politician i politici in B

Ha senso prendere da politician piuttosto che da govern come sottrazione di partenza, perché voglio tenere anche quelli che non hanno mai governato.

  • Trovare il nome delle città in cui non e' utilizzato il dollaro come moneta

  • Trovare i paesi che non confinano con l'italia

A: trovare i paesi che confinano con ITA
B: sottrarre A dall'elenco di tutti i country

Devo ottenere e perché ITA puo' apparire sia come che

Non c'e' bisogno di ridenominare l'id in e , perché conta il dominio del dato, non il suo nome.

  • Trovare i politici che hanno governato tutte le città di San Marino
    Vogliamo sapere quali sono i politici in relazione con tutte le città di San Marino, quindi serve la divisione.

Se non togliamo allora otteniamo quei politici che nello stesso anno hanno governato città di San Marino.

  • Trovare le citta' governate da piu' di un politico dopo il 2020

    Essendo che non siamo in grado di contare possiamo mettere in join con se' stessa, in modo da poterli comparare.

  • Trovare città che sono state governate da politici sia di destra sia di sinistra

    Soluzione alternativa con join

  • Trovare le pellicole prodotte in due paesi diversi (e' un self join, rivedi come si fa a chiamare in modi diversi la stessa tabella)

Relational algebra

Debuggare o capire codice e' 3 volte più difficile che scrivere codice, quindi se qualcuno scrive codice al massimo della sua capacita' e' per definizione incapace di debuggarlo o capirlo.
Quindi scrivi codice semplice.

Terminologia

Convalida: confronto del software con i requisiti informali posti dal committente.
Test di accettazione.

Verifica: confronto del software con le specifiche formali prodotte dall'analista.
Test di unita'.

Malfunzionamento (guasto / failure)

Funzionamento non corretto del programma, non del suo codice.
Esterno al sistema.

Quindi dire "c'e' un malfunzionamento alla riga 42" e' un uso improprio del termine.
Dire "invocando somma con 1 e 2 produce un malfunzionamento perche' si ottiene 2"
Non e' il comportamento previsto. Ad esempio l'Ariane 5 che esplode e' chiaro sia un malfunzionamento.

Difetto (anomalia / fault)

Legato al codice, e' condizione necessaria (ma non sufficiente) per il verificarsi di un malfunzionamento.
Ad esempio se ho una funzione raddoppia che fa

int raddoppia(int n) {
  return n * n;
}

non sempre ritorna un risultato sbagliato, per 2 e' corretta, per 3 no.
Pericolose perche' sembra stiano funzionando.
Sempre in Ariane 5 anomalia e' stata la conversione di un 64bit a 16bit del valore della velocita' orizzontale.

Sbaglio (mistake)

Causa di un difetto. In genere si tratta di un errore umano (concettuale, battitura, scarsa conoscenza del linguaggio).
Il riutilizzo della parte incriminata dall'Ariane 4 per l'Ariane 5, perche' Ariane 4 raggiungeva velocita' orizzontali non rappresentabili con 16bit.
E' possibile evitare che si ripeta, con dei processi.

Tecniche

Tecniche statiche: basate sull'analisi del codice:

  • metodi formali
  • analisi data flow
  • modelli statistici

Tecniche dinamiche: basate sull'esecuzione del programma eseguibile

  • testing
  • debugging (non di verifica)

Classificazione delle tecniche:

  • simplified properties: una versione semplificata del programma e' corretto
  • optimistic inaccuracy: "non sono sicuro, ma se non riesco a dimostrarti che non va bene allora va bene", che e' cio' che fa il testing
  • pessimistic inaccuracy: "se non riesco a dimostrarti formalmente che non c'e' quell'errore per me e' come se ci fosse", metodi formali

Metodi formali: tecniche che si prefiggono di provare l'assenza di anomalie nel prodotto finale
Testing: tecniche che si prefiggono di rilevare malfunzionamenti, o fornire fiducia nel prodotto
Debugging: tecniche che si prefiggono di localizzare le anomalie che causano malfunzionamenti rilevati in precedenza

Testing

Testing e' verifica di correttezza o validazione di affidabilita'.

Un programma si puo' dire corretto se aderisce alle specifiche per ogni dato appartenente al dominio di ingresso.

Un test ha successo quando riesce a rilevare uno o piu' malfunzionamenti presenti nel programma che non fossero gia' noti.

Test ideale: se il superamento del test (quindi il suo non successo) implica la correttezza del programma

Criterio di selezione test

Presi due test un criterio si dice affidabile se entrambi i test hanno successo o entrambi falliscono.

Presi due test un criterio si dice valido se, qualora non sia corretto, allora esiste almeno un selezionato in base al criterio che ha successo per il programma , quindi che rileva uno o piu' malfunzionamenti

Esempio

Dato

int raddoppia(int n) {
  return n * n;
}

Un criterio che seleziona sottoinsiemi di e' affidabile ma non valido.

Un criterio che seleziona sottoinsiemi di e' non affidabile ma valido.

Un criterio che seleziona sottoinsiemi finiti di con almeno un valore maggiore di e' affidabile e valido.

Tutto bello, ma...

Cosa succede se ho un test affidabile e valido? ( vuol dire che non trova problemi)

Quindi:

  • siccome e' affidabile se uno non trova, allora tutti non trovano
  • siccome e' valido se il programma fosse stato sbagliato allora almeno un test avrebbe dovuto trovare problema
  • esiste (o per ogni) non ha successo

Allora implica che il programma e' corretto, ma questa e' la definizione di test ideale, che abbiamo detto che non esiste.
Quindi non può esistere un criterio che sia contemporaneamente affidabile e valido!

Quindi l'unico caso in cui ho a priori senza conoscenza del test e' quando sto selezionando un unico test.

Quindi l'unico caso in cui ho a priori senza conoscenza dei test e' quando sto selezionando infiniti test.

Vogliamo quindi arrivare ad avvicinarci.

Utilita' di un test

Dobbiamo trovare una metrica che misuri la copertura di un criterio e ci permetta di

  • decidere quando smettere
  • decidere quale altro caso di test aggiungere per aumentare la copertura
  • confrontare la bonta' di test diversi

Un caso di test per evidenziare un malfunzionamento causato da una anomalia deve:

  1. eseguire il comando che contiene l'anomalia
  2. il punto precedente deve portare il sistema in uno stato scorretto
  3. il punto prececente deve portare a produrre un output diverso da quello atteso

Deriva dal punto 1. che voglio assicurarmi di stimolare tutti i comandi.

Voglio che i miei test siano limitati in merito al cammino che coprono, in modo tale che mi sia più agevole utilizzarli come punto di partenza per il debugging.

Criterio di copertura dei comandi (una riga)

Un test soddisfa il criterio di copertura dei comandi se e solo se ogni comando eseguibile del programma e' eseguito in corrispondenza di almeno un caso di test .

Soddisfare questo criterio non garantisce la correttezza del programma, esegue semplicemente tutte le righe di codice raggiungibili.

Criterio di copertura delle decisioni ( if, while, ...)

Un test soddisfa il criterio di copertura delle decisioni se e solo se ogni decisione effettiva viene resa sia vera che falsa in corrispondenza di almeno un caso di test .

Implica il criterio di copertura dei comandi.

Criterio di copertura delle condizioni (esempio una delle condizioni di un if)

Un test soddisfa il criterio di copertura delle condizioni se e solo se ogni singola condizione effettiva viene resa sia vera che falsa in corrispondenza di almeno un caso di test .

Si differenzia dal precedente Criterio di copertura delle decisioni perché si riferisce alle singole condizioni, non a tutta l'espressione oggetto di valutazione della condizione.

Non implica il criterio di copertura dei comandi.

Criterio di copertura delle decisioni e delle condizioni

Un test soddisfa il criterio di copertura delle decisioni e delle condizioni se e solo se ogni decisione vale sia vero che falso e ogni condizione che compare nelle decisioni del programma vale sia vero che falso per diversi casi di test .

Criterio di copertura delle condizioni composte

Un test soddisfa il criterio di copertura delle condizioni composte se e solo se ogni possibile composizione delle condizioni base vale sia vero che falso per diversi casi di test .

Per esempio per la condizione x != 0 && y < 3, vengono testati separatamente i casi \langle V,V \rangle,&nbsp;\langle V,F \rangle,&nbsp;\langle F,V \rangle,&nbsp;\langle F,F \rangle.

Data la natura combinatoria del criterio e' considerato non applicabile in pratica, oltre che alcune combinazioni potrebbero non avvenire, e quindi non avrebbe alcun senso testarle.

Criterio di copertura delle condizioni e delle decisioni modificate

Conto solo le combinazioni piu' rilevanti, cioe' quelle per cui la modifica di una condizione base porti a modificare l'esito della decisione.

Permette quindi di testare solamente per quei valori significativi che vanno a far cambiare la decisione.

Criterio di copertura dei cammini

Un test soddisfa il criterio di copertura dei cammini se e solo se ogni cammino del grafo di controllo del programma viene percorso per almeno un caso di .

Non applicabile in pratica.

Criterio di -copertura dei cicli

Un test soddisfa il criterio di -copertura se e solo se per ogni ciclo, abbiamo che viene eseguito , , ..., volte per almeno un caso di test.

Il criterio viene spesso applicato nella forma -copertura dei cicli:

  • zero iterazioni
  • una iterazione
  • piu' di una iterazione

Implicazioni tra criteri

Implicazioni tra criteri

Analisi Data Flow (DF)

E' analisi statica. Gli elementi da analizzare non sono infiniti come invece possono esserlo gli elementi nell'analisi dinamica.

Serve a capire come ottimizzare il codice, e per identificare potenziali errori.

Ci sono tre operazioni:

  • (definizione): il comando assegna un valore alla variabile, stesso dicasi per quando una variabile viene passata come parametro
  • (uso): il comando legge il contenuto di una variabile
  • (annullamento): il comando rende il valore della variabile invalido

Analizziamo le operazioni su x, x1, x2

void swap(int &x1, int &x2) {
    int x1; // (a) - shadowing di x1 parametro
    x3 = x1; // (u)
    x3 = x2;
    x2 = x1; // (u)
} // (a) - scope di x1 terminato
variabile DF
x auua
x1 ...dud...
x2 ...ddd...

x viene usata 2 volte senza essere prima stata definita. x2 viene definita più volte senza essere usata nel frattempo.

Lista di stati non corretti:

  • aa
  • au
  • da
  • dd

Criterio di copertura delle definizioni

insieme delle variabili definite nel nodo
e' l'insieme dei nodi tali che:

  • usato in
  • esiste un cammino da a , libero da definizioni di

Un test soddisfa il criterio di copertura delle definizioni se e solo se per ogni nodo e ogni variabile appartenente a include un caso di test che esegue un cammino libero da definizioni da ad almeno uno degli elementi di .

Ovvero, in termini umani: esiste almeno un uso di quella definizione.

Esempio

void main() {
	float a, b, x, y;
	read(x);
	read(y);
	a = x;
	b = y;
	while (a != b)
		if (a > b)
			a = a - b;
		else
			b = b - a;
	write(a);
}

Considerando la variabile aTODO AGGIUNGERE LINE NUMBERS


  • viene gratis
  • basta entrare una volta nel ciclo

CHIEDERE A NOTEBOOK ULTERIORI ESEMPI E DOMANE SU QUESTA PARTE

Criterio di copertura degli usi

Un test soddisfa il criterio di copertura degli usi se e solo se per ogni nodo e ogni variabile appartenente a per ogni elemento di include un caso di test che esegue un cammino libero da definizioni da a .

Non copre istruzioni che non sono usi.

CHIEDERE A NOTEBOOK ULTERIORI ESEMPI E DOMANE SU QUESTA PARTE

Esempio

void main() {
	float a, b, x, y;
	read(x);
	read(y);
	a = x;
	b = y;
	while (a != b)
		if (a > b)
			a = a - b;
		else
			b = b - a;
	write(a);
}

Considerando ancora la variabile


CHIEDERE A NOTEBOOK DI FARE UN ESEMPIO Di

Bebugging

Inseriamo errori dentro il codice prima di mandare il programma a chi lo deve testare. Chi testa sa che ci sono errori ma non sa dove sono.
Quindi nel mentre cerca questi errori ci sono ottime probabilità che ne trovi altri.

Tipicamente in questo scenario c'e' un team di testing separato dal team di sviluppo.

Problemi

  • potrebbe essere che ho messo errori più semplici di quelli che realmente voglio trovare
  • potrei usare strumenti sbagliati per trovare i bug

Analisi mutazionale

Viene generato un insieme di programmi simili al programma in esame.

Su di essi viene eseguito lo stesso test previsto per il programma :

  • se e' corretto allora i programmi in devono essere sbagliati
  • per almeno un caso di test devono quindi produrre un risultato diverso

Un test soddisfa il criterio di copertura dei mutanti se e solo se per ogni mutante esiste almeno un caso di test in la cui esecuzione produca per un risultato diverso da quello prodotto da .

La metrica e' la frazione di mutanti riconosciuta come diversa da sul totale di mutanti generati.

Voglio un mutante per ogni possibile difetto, virtualmente infiniti. I più semplici effettuano modifiche sintattiche che comportino modifiche semantiche.

L'onere di esecuzione e' molto forte.

Testing ed ereditarietà, testing e collegamento dinamico

Chiedere a NOTEBOOK di espandere questa parte

Class testing

Isolare la classe: costruiamo stub per renderla eseguibile indipendentemente dal contesto

I mock consentono di parallelizzare lo sviluppo di componenti che dipendono l'una dall'altra, a patto di avere dei contratti, espressi con le interfacce.

Copertura della classe

Chiedere a NOTEBOOK

Test funzionale

Sinonimo di blackbox.

Punto di partenza con cui effettuare ragionamenti per la copertura. Parto dai requisiti.

Verifica e convalida

Domande

    • cosa e' un test ideale? (+)
  • perche' TDD e' una buona tecnica? Tecnica di requisiti o tecnica di design o di progettazione? Cos'e'? (+)
  • quando un criterio di selezione è valido e quando è affidabile
  • terminologia degli errori
  • albero di copertura vs abero di raggiungibilità
  • criteri di selezione: proprietà
  • Terminologia di base di verifica e convalida. Sbaglio, errore, difetto, anomalia…, esempio in cui si presenta anomalia ma non (difetto o errore (?))

Definizione

TDD is an awareness of the gap between decision and feedback during programming, and techniques to control that gaps
-- Kent Beck

The gap is the time between when a decision is made and when you get if it was correct. In traditional development this gap could be huge, which makes debugging difficult and costly.

Techniques:

  • write tests before implementation code
  • work in very small increments (Red green refactor cycle)
  • getting rapid feedback on each decision
  • making course correction while the context is fresh in your mind

So TDD could be thought as a feedback mechanism.

Shrinking the gap allows to reduce decision fatigue and technical debt, as the latter could be expressed as decisions made without proper feedback.

Quotes

TDD e' una metodologia di sviluppo software, non di testing

Testing shows the presence, not the absence of bugs.
-- Dijkstra

This quote comes from here

Write tests until fear is transformed into boredom

TDD = test first + baby steps

Verifica e convalida

  • definizione di test affidabile e valido
  • criteri di selezione di test

Test

SUT is the subject under test, there can be only one SUT for a single test and it is never mocked.
DOC is the dependent on component, there can be many and they have to be mocked.

Si possono evitare di mockare:

  • enum
  • lambda

Red green refactor cycle

Failure is progress

Ask myself: "What set of tests, when passed, will demonstrate the presence of code we are confident will compute as expected?"

The rhythm of TDD (Red Green Refactor Red Green ...):

  • quickly add a test
  • run all tests and see the new one fail
  • make a little change
  • run all tests and se them all succeed
  • refactor to remove duplication

[Test Driven Development](https://martinfowler.com/bliki/TestDrivenDevelopment.html)

If in need to have a test go red use the triangulation technique, for example suppose the first expectation was the only one then we add the second:

public void testEquality() {
  assertTrue(new Dollar(5).equals(new Dollar(5)));
  assertTrue(new Dollar(5).equals(new Dollar(6))); // triangulation
}

Per ogni test ci deve essere una sola esecuzione del metodo che sto testando, cosi che quando qualcosa fallisce so esattamente dove andare a guardare, e non ci sono test che "falliscono a meta'".

Refactoring

Modifiche del codice senza cambiare funzionalita', per modificare qualche qualita' interna, avviene dopo il Green perche' se ottengo un Red dopo un Refactoring sono nell'incertezza.

Continuare a fare refactoring senza pieta'. Perche' per farlo ci vuole coraggio: "Scrivo il test e tra 5 10 minuti il test passa"

Non ci può essere un refactoring se non si parte da Green.
Nei refactoring non si possono modificare funzionalità.

Tempo

Un rosso deve essere ragionevolmente breve, indicativamente minore di 10' - 15'.
Trovarsi bloccati in questa fase puo' voler dire che si e' approcciato un problema troppo difficile, trovare una via piu' semplice.

Test double

The notion comes from "stunt double" used in movies.

Test double - controfigura per il testing: si mette al posto di DOC per testare in isolamento il SUT; utile quando DOC:

  • non esiste
  • fornisce dati non deterministici / non prevedibili
  • presentare situazioni difficilmente riproducibili (trasmissione, memoria, ...)
  • e' lento
  • potrebbe introdurre errori che non voglio considerare mentre testo SUT

Mock vs Stub

  • dummy: oggetto passato ma su cui non si fa alcuna asserzione

  • fake: possiede una implementazione funzionante, ma vive solo nel mondo dei test, ad esempio un database in memory per velocizzare i test

  • assenza di mock: si sta facendo verificazione dello stato

  • mock: verifica del comportamento ("questo metodo e' stato chiamato cosi?")

  • stub: forniscono risposte preconfezionate, e rispondono solo a quelle (parto dal vuoto)

  • spy: sono proxy di oggetti reali che consentono di loggarne le interazioni

Utilizzo

Crea un oggetto. Dichiaro cosa voglio che questo oggetto sappia fare.

Instrumentano il DOC, instrumentati per essere interrogabili in merito a cosa gli e' successo: "chi ti ha chiamato?", "in che ordine?", "quante volte?", ...

when(mockedObj.methodName(args)).thenXXX(values);

Per verificare quante volte un metodo viene chiamato

verify(mockedClass, times(1)).methodName(args)
// oppure
ArgumentCaptor<Person> arg = ArgumentCaptor
	.forClass(Person.class);
verify(mock).doSomething(arg.capture());
assertEquals("John", arg.getValue().getName());

Per evitare di "consumare l'iteratore" si puo' utilizzare questo metodo di utilita':

public static <T> void whenIter(Iterable<T> p, T... d) {
    when(p.iterator())
      .thenAnswer((Answer<Iterator<T>>) _ -> 
	      List.of(d).iterator());
}

Per mockare un costruttore

// si puo' passare a mockConstruction una serie di 
// parametri per mockare ad esempio eventuali altri 
// metodi che vengono usati nel costruttore di Tavolo
try (var mocked = Mockito.mockConstruction(Tavolo.class)) {
  Partita p = new Partita();
  Card c = mock();
  Tavolo t = mocked.constructed().getFirst();
  // ...
}

Spy

Wrappa un oggetto reale. Dichiaro cosa voglio che non sappia fare (svuoto).

Se il soggetto e' l'oggetto under test allora

stub : spy = riempire : svuotare

Meglio partire dal vuoto, cioe' stub.

Testare metodi privati

Sono testati indirettamente, a fronte di chiamate dall'esterno.
Voglio poter cambiare i metodi privati senza troppo sforzo, in modo da fare refactor senza incorrere in grossi attriti.

Usare extracting quando si vuole testare una proprieta' privata, e

AssertionsForClassTypes.assertThat(player)
	.extracting("personalDeck", as(LIST))
	.containsExactly(Card.of("1B"), Card.of("2B"));

quando si sta testando una classe che implementa interfacce, perche' altrimenti l'assertThat "solito" non riesce a estrarre la proprieta'.

Esporre variabili d'istanza / metodi

var m = Game
	.class
	.getDeclaredMethod("distributeInitialCards");  
m.setAccessible(true);  
m.invoke(game);

Da farsi solo in test, perche' altrimenti a causa della reflection puo' rompere tutte le astrazioni.

Patterns

Il codice duplicato e' anche dentro i test, non solo nel sorgente!

Isolation

Good tests are written in isolation, so if one fails the rest will continue as if nothing happened: the idea is not to pollute a global state from which tests take their fixtures.

One broken test one problem.
Two broken tests two problems.

If tests are written in isolation then the order in which they're run does not matter.

Test list

Ariadne's thread: Offload your brain list into a written one, on paper, jot down critical issues, pain points, etc... ; you don't want to put the list into tests right away, always follow the "climber rule": out of four among feet and hands always have at least three attached to the wall.

Assert first

Write the assert first and then work your way upwards through the test.
This approach allows to concentrate on the goal and force the preconditions to come out almost on their own.

Data

Make use of data to tell a clearer story, make your intentions evident.
For example split whole numbers in elementary operations to make the reader aware of "where did that 372.68 come from?".

Problema degli unit test

il problema degli unit test

Sempre bene usare test di integrazione per asserire riguardo la correttezza del programma.

A volte puo' succedere che eseguendo un nuovo test (scommentato ad esempio come vediamo a laboratorio), questo passi senza aver seguito il red green refactor red etc.Red green green ... wat?
Vuol dire che magari la mia soluzione probabilmente ha implementato piu' del necessario.
Quindi vuol dire che non ho scritto la soluzione piu' semplice per far passare il test.

LLM prompts

I want to make a learning exercise with you: I need to learn TDD for a university course, I want you to act like you're not into it and will try to push back, while I will try to convince you. Ready?

TDD